diff --git a/facebook/build.gradle b/facebook/build.gradle index 8754881ceb..5c9de2f29a 100644 --- a/facebook/build.gradle +++ b/facebook/build.gradle @@ -8,10 +8,10 @@ project.group = 'com.facebook.android' dependencies { // Facebook Dependencies - compile 'com.android.support:support-v4:23.4.0' - compile 'com.android.support:appcompat-v7:23.4.0' - compile 'com.android.support:cardview-v7:23.4.0' - compile 'com.android.support:customtabs:23.4.0' + compile 'com.android.support:support-v4:25.0.0' + compile 'com.android.support:appcompat-v7:25.0.0' + compile 'com.android.support:cardview-v7:25.0.0' + compile 'com.android.support:customtabs:25.0.0' compile 'com.parse.bolts:bolts-android:1.4.0' // Unit Tests diff --git a/facebook/src/androidTest/java/com/facebook/appevents/UpdateUserPropertiesTests.java b/facebook/src/androidTest/java/com/facebook/appevents/UpdateUserPropertiesTests.java new file mode 100644 index 0000000000..ebc23cd871 --- /dev/null +++ b/facebook/src/androidTest/java/com/facebook/appevents/UpdateUserPropertiesTests.java @@ -0,0 +1,55 @@ +/** + * Copyright (c) 2014-present, Facebook, Inc. All rights reserved. + * + * You are hereby granted a non-exclusive, worldwide, royalty-free license to use, + * copy, modify, and distribute this software in source code or binary form for use + * in connection with the web services and APIs provided by Facebook. + * + * As with any software that integrates with the Facebook platform, your use of + * this software is subject to the Facebook Developer Principles and Policies + * [http://developers.facebook.com/policy/]. This copyright notice shall be + * included in all copies or substantial portions of the software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package com.facebook.appevents; + +import android.os.Bundle; + +import com.facebook.FacebookTestCase; +import com.facebook.GraphRequest; +import com.facebook.GraphResponse; +import com.facebook.TestBlocker; + +public class UpdateUserPropertiesTests extends FacebookTestCase { + public void testUserUpdateProperties() throws Exception { + final TestBlocker blocker = getTestBlocker(); + Bundle parameters = new Bundle(); + parameters.putString("custom_value", "1"); + AppEventsLogger.setUserID("1"); + AppEventsLogger.updateUserProperties( + parameters, + getApplicationId(), + new GraphRequest.Callback() { + @Override + public void onCompleted(GraphResponse response) { + if (response.getError() != null) { + blocker.setException(response.getError().getException()); + } + + blocker.signal(); + } + }); + + blocker.waitForSignals(1); + if (blocker.getException() != null) { + throw blocker.getException(); + } + } +} diff --git a/facebook/src/androidTest/java/com/facebook/internal/UtilityTest.java b/facebook/src/androidTest/java/com/facebook/internal/UtilityTest.java index 464531a214..3480a0f274 100644 --- a/facebook/src/androidTest/java/com/facebook/internal/UtilityTest.java +++ b/facebook/src/androidTest/java/com/facebook/internal/UtilityTest.java @@ -29,8 +29,8 @@ public class UtilityTest extends FacebookTestCase { @LargeTest public void testFetchedAppSettingsErrorClassification() throws Exception { - Utility.FetchedAppSettings fetchedAppSettings = - Utility.queryAppSettings(getApplicationId(), false); + FetchedAppSettings fetchedAppSettings = + FetchedAppSettingsManager.queryAppSettings(getApplicationId(), false); FacebookRequestErrorClassification errorClassification = fetchedAppSettings .getErrorClassification(); assertNotNull(errorClassification); diff --git a/facebook/src/main/java/com/facebook/AccessToken.java b/facebook/src/main/java/com/facebook/AccessToken.java index 523b34c6f0..289e99e7c3 100644 --- a/facebook/src/main/java/com/facebook/AccessToken.java +++ b/facebook/src/main/java/com/facebook/AccessToken.java @@ -23,8 +23,6 @@ import android.annotation.SuppressLint; import android.content.Intent; import android.os.Bundle; -import android.os.Handler; -import android.os.Looper; import android.os.Parcel; import android.os.Parcelable; import android.support.annotation.Nullable; @@ -37,7 +35,13 @@ import org.json.JSONException; import org.json.JSONObject; -import java.util.*; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Date; +import java.util.HashSet; +import java.util.List; +import java.util.Set; /** * This class represents an immutable access token for using Facebook APIs. It also includes diff --git a/facebook/src/main/java/com/facebook/FacebookRequestError.java b/facebook/src/main/java/com/facebook/FacebookRequestError.java index 562ac169b3..c715085016 100644 --- a/facebook/src/main/java/com/facebook/FacebookRequestError.java +++ b/facebook/src/main/java/com/facebook/FacebookRequestError.java @@ -24,6 +24,8 @@ import android.os.Parcelable; import com.facebook.internal.FacebookRequestErrorClassification; +import com.facebook.internal.FetchedAppSettings; +import com.facebook.internal.FetchedAppSettingsManager; import com.facebook.internal.Utility; import org.json.JSONException; @@ -426,8 +428,8 @@ static FacebookRequestError checkResponseAndCreateError( static synchronized FacebookRequestErrorClassification getErrorClassification() { FacebookRequestErrorClassification errorClassification; - Utility.FetchedAppSettings appSettings = - Utility.getAppSettingsWithoutQuery(FacebookSdk.getApplicationId()); + FetchedAppSettings appSettings = + FetchedAppSettingsManager.getAppSettingsWithoutQuery(FacebookSdk.getApplicationId()); if (appSettings == null) { return FacebookRequestErrorClassification.getDefaultErrorClassification(); } diff --git a/facebook/src/main/java/com/facebook/FacebookSdk.java b/facebook/src/main/java/com/facebook/FacebookSdk.java index 2e143b371a..f8d56db401 100644 --- a/facebook/src/main/java/com/facebook/FacebookSdk.java +++ b/facebook/src/main/java/com/facebook/FacebookSdk.java @@ -27,12 +27,12 @@ import android.content.pm.PackageManager; import android.content.pm.Signature; import android.os.AsyncTask; -import android.text.TextUtils; import android.util.Base64; import android.util.Log; import com.facebook.appevents.AppEventsLogger; import com.facebook.internal.AppEventsLoggerUtility; +import com.facebook.internal.FetchedAppSettingsManager; import com.facebook.internal.LockOnGetVariable; import com.facebook.internal.BoltsMeasurementEventListener; import com.facebook.internal.AttributionIdentifiers; @@ -40,7 +40,6 @@ import com.facebook.internal.ServerProtocol; import com.facebook.internal.Utility; import com.facebook.internal.Validate; -import com.facebook.internal.WebDialog; import org.json.JSONException; import org.json.JSONObject; @@ -212,7 +211,7 @@ public static synchronized void sdkInitialize( sdkInitialized = true; // Load app settings from network so that dialog configs are available - Utility.loadAppSettingsAsync(FacebookSdk.applicationContext, applicationId); + FetchedAppSettingsManager.loadAppSettingsAsync(FacebookSdk.applicationContext, applicationId); // Fetch available protocol versions from the apps on the device NativeProtocol.updateAllAvailableProtocolVersionsAsync(); diff --git a/facebook/src/main/java/com/facebook/FacebookSdkVersion.java b/facebook/src/main/java/com/facebook/FacebookSdkVersion.java index 1b0ebe7653..6e0d5c43c9 100644 --- a/facebook/src/main/java/com/facebook/FacebookSdkVersion.java +++ b/facebook/src/main/java/com/facebook/FacebookSdkVersion.java @@ -21,5 +21,5 @@ package com.facebook; final class FacebookSdkVersion { - public static final String BUILD = "4.16.1"; + public static final String BUILD = "4.17.0"; } diff --git a/facebook/src/main/java/com/facebook/GraphResponse.java b/facebook/src/main/java/com/facebook/GraphResponse.java index 34868efb40..f7de20e118 100644 --- a/facebook/src/main/java/com/facebook/GraphResponse.java +++ b/facebook/src/main/java/com/facebook/GraphResponse.java @@ -359,8 +359,7 @@ private static List createResponsesFromObject( } if (!(object instanceof JSONArray) || ((JSONArray) object).length() != numRequests) { - FacebookException exception = new FacebookException("Unexpected number of results"); - throw exception; + throw new FacebookException("Unexpected number of results"); } JSONArray jsonArray = (JSONArray) object; diff --git a/facebook/src/main/java/com/facebook/Profile.java b/facebook/src/main/java/com/facebook/Profile.java index 919ba16868..22488c6693 100644 --- a/facebook/src/main/java/com/facebook/Profile.java +++ b/facebook/src/main/java/com/facebook/Profile.java @@ -21,7 +21,6 @@ package com.facebook; import android.net.Uri; -import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; import android.support.annotation.Nullable; diff --git a/facebook/src/main/java/com/facebook/appevents/AnalyticsUserIDStore.java b/facebook/src/main/java/com/facebook/appevents/AnalyticsUserIDStore.java new file mode 100644 index 0000000000..960ab1c760 --- /dev/null +++ b/facebook/src/main/java/com/facebook/appevents/AnalyticsUserIDStore.java @@ -0,0 +1,113 @@ +/** + * Copyright (c) 2014-present, Facebook, Inc. All rights reserved. + * + * You are hereby granted a non-exclusive, worldwide, royalty-free license to use, + * copy, modify, and distribute this software in source code or binary form for use + * in connection with the web services and APIs provided by Facebook. + * + * As with any software that integrates with the Facebook platform, your use of + * this software is subject to the Facebook Developer Principles and Policies + * [http://developers.facebook.com/policy/]. This copyright notice shall be + * included in all copies or substantial portions of the software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package com.facebook.appevents; + +import android.content.SharedPreferences; +import android.preference.PreferenceManager; +import android.util.Log; + +import com.facebook.FacebookSdk; +import com.facebook.appevents.internal.AppEventUtility; + +import java.util.concurrent.locks.ReentrantReadWriteLock; + +class AnalyticsUserIDStore { + private static final String TAG = AnalyticsUserIDStore.class.getSimpleName(); + private static final String ANALYTICS_USER_ID_KEY = + "com.facebook.appevents.AnalyticsUserIDStore.userID"; + + private static ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); + private static String userID; + private static volatile boolean initialized = false; + + public static void initStore() { + if (initialized) { + return; + } + + AppEventsLogger.getAnalyticsExecutor().execute(new Runnable() { + @Override + public void run() { + initAndWait(); + } + }); + } + + public static void setUserID(final String id) { + AppEventUtility.assertIsNotMainThread(); + if (!initialized) { + Log.w(TAG, "initStore should have been called before calling setUserID"); + initAndWait(); + } + + AppEventsLogger.getAnalyticsExecutor().execute(new Runnable() { + @Override + public void run() { + lock.writeLock().lock(); + try { + userID = id; + SharedPreferences sharedPreferences = PreferenceManager + .getDefaultSharedPreferences( + FacebookSdk.getApplicationContext()); + SharedPreferences.Editor editor = sharedPreferences.edit(); + editor.putString(ANALYTICS_USER_ID_KEY, userID); + editor.apply(); + } finally { + lock.writeLock().unlock(); + } + } + }); + } + + public static String getUserID() { + if (!initialized) { + Log.w(TAG, "initStore should have been called before calling setUserID"); + initAndWait(); + } + + lock.readLock().lock(); + try { + return userID; + } finally { + lock.readLock().unlock(); + } + } + + private static void initAndWait() { + if (initialized) { + return; + } + + lock.writeLock().lock(); + try { + if (initialized) { + return; + } + + SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences( + FacebookSdk.getApplicationContext()); + userID = sharedPreferences.getString(ANALYTICS_USER_ID_KEY, null); + initialized = true; + } finally { + lock.writeLock().unlock(); + } + } +} diff --git a/facebook/src/main/java/com/facebook/appevents/AppEvent.java b/facebook/src/main/java/com/facebook/appevents/AppEvent.java index 52d2d7c984..af8ffd51b0 100644 --- a/facebook/src/main/java/com/facebook/appevents/AppEvent.java +++ b/facebook/src/main/java/com/facebook/appevents/AppEvent.java @@ -124,7 +124,7 @@ private static void validateIdentifier(String identifier) throws FacebookExcepti ); } - boolean alreadyValidated = false; + boolean alreadyValidated; synchronized (validatedIdentifiers) { alreadyValidated = validatedIdentifiers.contains(identifier); } @@ -174,6 +174,11 @@ private static JSONObject getJSONObjectForAppEvent( eventObject.put("_implicitlyLogged", "1"); } + String externalAnalyticsUserId = AppEventsLogger.getUserID(); + if (externalAnalyticsUserId != null) { + eventObject.put("_app_user_id", externalAnalyticsUserId); + } + if (parameters != null) { for (String key : parameters.keySet()) { @@ -254,7 +259,7 @@ private String calculateChecksum() { private static String md5Checksum(String toHash ) { - String hash = null; + String hash; try { MessageDigest digest = MessageDigest.getInstance("MD5"); diff --git a/facebook/src/main/java/com/facebook/appevents/AppEventQueue.java b/facebook/src/main/java/com/facebook/appevents/AppEventQueue.java index 6906119963..25656822b9 100644 --- a/facebook/src/main/java/com/facebook/appevents/AppEventQueue.java +++ b/facebook/src/main/java/com/facebook/appevents/AppEventQueue.java @@ -31,6 +31,8 @@ import com.facebook.GraphRequest; import com.facebook.GraphResponse; import com.facebook.LoggingBehavior; +import com.facebook.internal.FetchedAppSettings; +import com.facebook.internal.FetchedAppSettingsManager; import com.facebook.internal.Logger; import com.facebook.internal.Utility; @@ -38,7 +40,6 @@ import org.json.JSONException; import java.util.ArrayList; -import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.concurrent.Executors; @@ -194,8 +195,8 @@ private static GraphRequest buildRequestForSession( final FlushStatistics flushState) { String applicationId = accessTokenAppId.getApplicationId(); - Utility.FetchedAppSettings fetchedAppSettings = - Utility.queryAppSettings(applicationId, false); + FetchedAppSettings fetchedAppSettings = + FetchedAppSettingsManager.queryAppSettings(applicationId, false); final GraphRequest postRequest = GraphRequest.newPostRequest( null, diff --git a/facebook/src/main/java/com/facebook/appevents/AppEventStore.java b/facebook/src/main/java/com/facebook/appevents/AppEventStore.java index 35d6bc518e..fb5f45ffeb 100644 --- a/facebook/src/main/java/com/facebook/appevents/AppEventStore.java +++ b/facebook/src/main/java/com/facebook/appevents/AppEventStore.java @@ -21,15 +21,12 @@ package com.facebook.appevents; import android.content.Context; -import android.os.Looper; import android.util.Log; import com.facebook.FacebookSdk; import com.facebook.appevents.internal.AppEventUtility; import com.facebook.internal.Utility; -import junit.framework.Assert; - import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.FileNotFoundException; @@ -38,8 +35,6 @@ import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.ObjectStreamClass; -import java.util.HashMap; -import java.util.List; class AppEventStore { private static final String TAG = AppEventStore.class.getName(); diff --git a/facebook/src/main/java/com/facebook/appevents/AppEventsLogger.java b/facebook/src/main/java/com/facebook/appevents/AppEventsLogger.java index 2652736c85..b3d7ea1ebb 100644 --- a/facebook/src/main/java/com/facebook/appevents/AppEventsLogger.java +++ b/facebook/src/main/java/com/facebook/appevents/AppEventsLogger.java @@ -43,6 +43,7 @@ import com.facebook.appevents.internal.ActivityLifecycleTracker; import com.facebook.internal.AttributionIdentifiers; import com.facebook.internal.BundleJSONConverter; +import com.facebook.internal.FetchedAppSettingsManager; import com.facebook.internal.Logger; import com.facebook.internal.Utility; import com.facebook.internal.Validate; @@ -222,6 +223,8 @@ public static void activateApp(Application application, String applicationId) { "activateApp"); } + AnalyticsUserIDStore.initStore(); + if (applicationId == null) { applicationId = FacebookSdk.getApplicationId(); } @@ -280,6 +283,8 @@ public static void activateApp(Context context, String applicationId) { throw new IllegalArgumentException("Both context and applicationId must be non-null"); } + AnalyticsUserIDStore.initStore(); + if ((context instanceof Activity)) { setSourceApplication((Activity) context); } else { @@ -695,6 +700,94 @@ static String getPushNotificationsRegistrationId() { } } + /** + * Sets a user id to associate with all app events. This can be used to associate your own + * user id with the app events logged from this instance of an application. + * + * The user ID will be persisted between application instantces. + * + * @param userID A User ID + */ + public static void setUserID(final String userID) { + AnalyticsUserIDStore.setUserID(userID); + } + + /** + * Returns the set user id else null. + */ + public static String getUserID() { + return AnalyticsUserIDStore.getUserID(); + } + + /** + * Clears the currently set user id. + */ + public static void clearUserID() { + AnalyticsUserIDStore.setUserID(null); + } + + public static void updateUserProperties( + Bundle parameters, + GraphRequest.Callback callback) { + updateUserProperties( + parameters, + FacebookSdk.getApplicationId(), + callback); + } + + public static void updateUserProperties( + final Bundle parameters, + final String applicationID, + final GraphRequest.Callback callback) { + final String userID = getUserID(); + if (userID == null || userID.isEmpty()) { + Logger.log( + LoggingBehavior.APP_EVENTS, + TAG, + "AppEventsLogger userID cannot be null or empty"); + return; + } + + getAnalyticsExecutor().execute(new Runnable() { + @Override + public void run() { + Bundle userPropertiesParams = new Bundle(); + userPropertiesParams.putString("user_unique_id", userID); + userPropertiesParams.putBundle("custom_data", parameters); + // This call must be run on the background thread + AttributionIdentifiers identifiers = + AttributionIdentifiers.getAttributionIdentifiers( + FacebookSdk.getApplicationContext()); + if (identifiers != null && identifiers.getAndroidAdvertiserId() != null) { + userPropertiesParams.putString( + "advertiser_id", + identifiers.getAndroidAdvertiserId()); + } + + Bundle data = new Bundle(); + try { + JSONObject userData = BundleJSONConverter.convertToJSON(userPropertiesParams); + JSONArray dataArray = new JSONArray(); + dataArray.put(userData); + + data.putString( + "data", dataArray.toString()); + } catch (JSONException ex) { + throw new FacebookException("Failed to construct request", ex); + } + + GraphRequest request = new GraphRequest( + AccessToken.getCurrentAccessToken(), + String.format(Locale.US, "%s/user_properties", applicationID), + data, + HttpMethod.POST, + callback); + request.setSkipClientToken(true); + request.executeAsync(); + } + }); + } + /** * This method is intended only for internal use by the Facebook SDK and other use is * unsupported. @@ -776,7 +869,7 @@ public void run() { } for (String applicationId : applicationIds) { - Utility.queryAppSettings(applicationId, true); + FetchedAppSettingsManager.queryAppSettings(applicationId, true); } } }; diff --git a/facebook/src/main/java/com/facebook/appevents/internal/ActivityLifecycleTracker.java b/facebook/src/main/java/com/facebook/appevents/internal/ActivityLifecycleTracker.java index f9e27b90ba..e174c4c7e0 100644 --- a/facebook/src/main/java/com/facebook/appevents/internal/ActivityLifecycleTracker.java +++ b/facebook/src/main/java/com/facebook/appevents/internal/ActivityLifecycleTracker.java @@ -24,15 +24,14 @@ import android.app.Application; import android.content.Context; import android.os.Bundle; -import android.os.Looper; import android.util.Log; import com.facebook.FacebookSdk; import com.facebook.appevents.AppEventsLogger; +import com.facebook.internal.FetchedAppSettings; +import com.facebook.internal.FetchedAppSettingsManager; import com.facebook.internal.Utility; -import junit.framework.Assert; - import java.util.UUID; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; @@ -268,8 +267,8 @@ public void run() { } private static int getSessionTimeoutInSeconds() { - Utility.FetchedAppSettings settings = - Utility.getAppSettingsWithoutQuery(FacebookSdk.getApplicationId()); + FetchedAppSettings settings = + FetchedAppSettingsManager.getAppSettingsWithoutQuery(FacebookSdk.getApplicationId()); if (settings == null) { return Constants.getDefaultAppEventsSessionTimeoutInSeconds(); } diff --git a/facebook/src/main/java/com/facebook/appevents/internal/AutomaticAnalyticsLogger.java b/facebook/src/main/java/com/facebook/appevents/internal/AutomaticAnalyticsLogger.java index b2d069d155..7b886dc511 100644 --- a/facebook/src/main/java/com/facebook/appevents/internal/AutomaticAnalyticsLogger.java +++ b/facebook/src/main/java/com/facebook/appevents/internal/AutomaticAnalyticsLogger.java @@ -24,8 +24,8 @@ import android.os.Bundle; import com.facebook.appevents.AppEventsLogger; -import com.facebook.internal.Utility; -import com.facebook.internal.Utility.FetchedAppSettings; +import com.facebook.internal.FetchedAppSettings; +import com.facebook.internal.FetchedAppSettingsManager; class AutomaticAnalyticsLogger { @@ -35,7 +35,7 @@ public static void logActivityTimeSpentEvent( String activityName, long timeSpentInSeconds) { AppEventsLogger l = AppEventsLogger.newLogger(context); - final FetchedAppSettings settings = Utility.queryAppSettings(appId, false); + final FetchedAppSettings settings = FetchedAppSettingsManager.queryAppSettings(appId, false); if (settings.getAutomaticLoggingEnabled() && timeSpentInSeconds > 0) { Bundle params = new Bundle(1); params.putCharSequence(Constants.AA_TIME_SPENT_SCREEN_PARAMETER_NAME, activityName); diff --git a/facebook/src/main/java/com/facebook/appevents/internal/SessionLogger.java b/facebook/src/main/java/com/facebook/appevents/internal/SessionLogger.java index 8b88f5527a..fb9d457745 100644 --- a/facebook/src/main/java/com/facebook/appevents/internal/SessionLogger.java +++ b/facebook/src/main/java/com/facebook/appevents/internal/SessionLogger.java @@ -20,17 +20,13 @@ package com.facebook.appevents.internal; -import android.app.Activity; import android.content.Context; import android.os.Bundle; import android.text.format.DateUtils; -import com.facebook.FacebookSdk; import com.facebook.LoggingBehavior; import com.facebook.appevents.AppEventsConstants; -import com.facebook.appevents.AppEventsLogger; import com.facebook.internal.Logger; -import com.facebook.internal.Utility; import java.util.Locale; diff --git a/facebook/src/main/java/com/facebook/applinks/FacebookAppLinkResolver.java b/facebook/src/main/java/com/facebook/applinks/FacebookAppLinkResolver.java index 807d92f1f1..6045c7129c 100644 --- a/facebook/src/main/java/com/facebook/applinks/FacebookAppLinkResolver.java +++ b/facebook/src/main/java/com/facebook/applinks/FacebookAppLinkResolver.java @@ -28,15 +28,20 @@ import com.facebook.GraphRequest; import com.facebook.GraphResponse; -import bolts.AppLink; -import bolts.AppLinkResolver; -import bolts.Continuation; -import bolts.Task; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; -import java.util.*; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; + +import bolts.AppLink; +import bolts.AppLinkResolver; +import bolts.Continuation; +import bolts.Task; /** * Provides an implementation for the {@link AppLinkResolver AppLinkResolver} interface that uses @@ -93,7 +98,7 @@ public Task> getAppLinkFromUrlsInBackground(List uris) { StringBuilder graphRequestFields = new StringBuilder(); for (Uri uri : uris) { - AppLink appLink = null; + AppLink appLink; synchronized (cachedAppLinks) { appLink = cachedAppLinks.get(uri); } @@ -153,7 +158,7 @@ public void onCompleted(GraphResponse response) { continue; } - JSONObject urlData = null; + JSONObject urlData; try { urlData = responseJson.getJSONObject(uri.toString()); JSONObject appLinkData = urlData.getJSONObject(APP_LINK_KEY); diff --git a/facebook/src/main/java/com/facebook/devicerequests/internal/DeviceRequestsHelper.java b/facebook/src/main/java/com/facebook/devicerequests/internal/DeviceRequestsHelper.java new file mode 100644 index 0000000000..8c0803f6ab --- /dev/null +++ b/facebook/src/main/java/com/facebook/devicerequests/internal/DeviceRequestsHelper.java @@ -0,0 +1,177 @@ +/** + * Copyright (c) 2014-present, Facebook, Inc. All rights reserved. + * + * You are hereby granted a non-exclusive, worldwide, royalty-free license to use, + * copy, modify, and distribute this software in source code or binary form for use + * in connection with the web services and APIs provided by Facebook. + * + * As with any software that integrates with the Facebook platform, your use of + * this software is subject to the Facebook Developer Principles and Policies + * [http://developers.facebook.com/policy/]. This copyright notice shall be + * included in all copies or substantial portions of the software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package com.facebook.devicerequests.internal; + +import android.annotation.TargetApi; +import android.content.Context; +import android.net.nsd.NsdManager; +import android.net.nsd.NsdServiceInfo; +import android.os.Build; + +import com.facebook.FacebookSdk; +import com.facebook.internal.FetchedAppSettingsManager; +import com.facebook.internal.SmartLoginOption; +import com.facebook.internal.Utility; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.HashMap; + +/** + * com.facebook.devicerequests.internal is solely for the use of other packages within the + * Facebook SDK for Android. Use of any of the classes in this package is + * unsupported, and they may be modified or removed without warning at any time. + */ +public class DeviceRequestsHelper { + + public static final String DEVICE_INFO_PARAM = "device_info"; + + static final String DEVICE_INFO_DEVICE = "device"; + static final String DEVICE_INFO_MODEL = "model"; + + static final String SDK_HEADER = "fbsdk"; + static final String SDK_FLAVOR = "android"; + + static final String SERVICE_TYPE = "_fb._tcp."; + + private static HashMap deviceRequestsListeners = + new HashMap<>(); + + public static String getDeviceInfo() { + // Device info + // We don't need all the information in Utility.setAppEventExtendedDeviceInfoParameters + // We only want the model so we can show it to the user, so they know which device + // the login request comes from + JSONObject deviceInfo = new JSONObject(); + try { + deviceInfo.put(DEVICE_INFO_DEVICE, Build.DEVICE); + deviceInfo.put(DEVICE_INFO_MODEL, Build.MODEL); + } catch (JSONException ignored) { + } + + return deviceInfo.toString(); + } + + public static boolean startAdvertisementService(String userCode) { + if (isAvailable()) { + return startAdvertisementServiceImpl(userCode); + } + + return false; + } + + /* + returns true if smart login is enabled and the android api level is supported. + */ + public static boolean isAvailable() { + return (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) && + FetchedAppSettingsManager.getAppSettingsWithoutQuery(FacebookSdk.getApplicationId()). + getSmartLoginOptions().contains(SmartLoginOption.Enabled); + } + + public static void cleanUpAdvertisementService(String userCode) { + cleanUpAdvertisementServiceImpl(userCode); + } + + @TargetApi(16) + private static boolean startAdvertisementServiceImpl(final String userCode) { + if (deviceRequestsListeners.containsKey(userCode)) { + return true; + } + + // Dots in the version will mess up the Bonjour DNS record parsing + String sdkVersion = FacebookSdk.getSdkVersion().replace('.', '|'); + // Other SDKs that adopt this feature should use different flavor name + // The whole name should not exceed 60 characters + final String nsdServiceName = String.format("%s_%s_%s", + // static identifier + SDK_HEADER, + // sdk type and version + // client app parses the string based on this version + String.format("%s-%s", + SDK_FLAVOR, + sdkVersion + ), + + // Additional fields should be added here + + // short code for the login flow + userCode + ); + + NsdServiceInfo nsdLoginAdvertisementService = new NsdServiceInfo(); + nsdLoginAdvertisementService.setServiceType(SERVICE_TYPE); + nsdLoginAdvertisementService.setServiceName(nsdServiceName); + nsdLoginAdvertisementService.setPort(80); + + NsdManager nsdManager = (NsdManager)FacebookSdk + .getApplicationContext() + .getSystemService(Context.NSD_SERVICE); + + NsdManager.RegistrationListener nsdRegistrationListener = + new NsdManager.RegistrationListener() { + @Override + public void onServiceRegistered(NsdServiceInfo NsdServiceInfo) { + // Android may have changed the service name in order to resolve a conflict + if (!nsdServiceName.equals(NsdServiceInfo.getServiceName())) { + cleanUpAdvertisementService(userCode); + } + } + + @Override + public void onServiceUnregistered(NsdServiceInfo serviceInfo) { + } + + @Override + public void onRegistrationFailed(NsdServiceInfo serviceInfo, int errorCode) { + cleanUpAdvertisementService(userCode); + } + + @Override + public void onUnregistrationFailed(NsdServiceInfo serviceInfo, int errorCode) { + } + }; + + deviceRequestsListeners.put(userCode, nsdRegistrationListener); + + nsdManager.registerService(nsdLoginAdvertisementService, + NsdManager.PROTOCOL_DNS_SD, + nsdRegistrationListener); + + return true; + } + + @TargetApi(16) + private static void cleanUpAdvertisementServiceImpl(String userCode) { + NsdManager.RegistrationListener nsdRegistrationListener = + deviceRequestsListeners.get(userCode); + if (nsdRegistrationListener != null) { + NsdManager nsdManager = (NsdManager)FacebookSdk + .getApplicationContext() + .getSystemService(Context.NSD_SERVICE); + + nsdManager.unregisterService(nsdRegistrationListener); + + deviceRequestsListeners.remove(userCode); + } + } +} diff --git a/facebook/src/main/java/com/facebook/internal/AnalyticsEvents.java b/facebook/src/main/java/com/facebook/internal/AnalyticsEvents.java index 753eec00e2..bf23211b29 100644 --- a/facebook/src/main/java/com/facebook/internal/AnalyticsEvents.java +++ b/facebook/src/main/java/com/facebook/internal/AnalyticsEvents.java @@ -113,4 +113,6 @@ public class AnalyticsEvents { public static final String EVENT_DEVICE_SHARE_BUTTON_CREATE = "fb_device_share_button_create"; public static final String EVENT_DEVICE_SHARE_BUTTON_DID_TAP= "fb_device_share_button_did_tap"; + + public static final String EVENT_SMART_LOGIN_SERVICE = "fb_smart_login_service"; } diff --git a/facebook/src/main/java/com/facebook/internal/CollectionMapper.java b/facebook/src/main/java/com/facebook/internal/CollectionMapper.java index 6ab0f110af..191073e35e 100644 --- a/facebook/src/main/java/com/facebook/internal/CollectionMapper.java +++ b/facebook/src/main/java/com/facebook/internal/CollectionMapper.java @@ -22,7 +22,6 @@ import com.facebook.FacebookException; -import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.List; diff --git a/facebook/src/main/java/com/facebook/internal/DialogPresenter.java b/facebook/src/main/java/com/facebook/internal/DialogPresenter.java index d2f934bd3f..065de8b337 100644 --- a/facebook/src/main/java/com/facebook/internal/DialogPresenter.java +++ b/facebook/src/main/java/com/facebook/internal/DialogPresenter.java @@ -25,7 +25,6 @@ import android.content.Intent; import android.net.Uri; import android.os.Bundle; -import android.support.v4.app.Fragment; import com.facebook.FacebookActivity; import com.facebook.FacebookException; @@ -224,8 +223,8 @@ private static Uri getDialogWebFallbackUri(DialogFeature feature) { String action = feature.getAction(); String applicationId = FacebookSdk.getApplicationId(); - Utility.DialogFeatureConfig config = - Utility.getDialogFeatureConfig(applicationId, action, featureName); + FetchedAppSettings.DialogFeatureConfig config = + FetchedAppSettings.getDialogFeatureConfig(applicationId, action, featureName); Uri fallbackUrl = null; if (config != null) { fallbackUrl = config.getFallbackUrl(); @@ -251,8 +250,8 @@ private static int[] getVersionSpecForFeature( DialogFeature feature) { // Return the value from DialogFeatureConfig if available. Otherwise, just // default to the min-version - Utility.DialogFeatureConfig config = - Utility.getDialogFeatureConfig(applicationId, actionName, feature.name()); + FetchedAppSettings.DialogFeatureConfig config = + FetchedAppSettings.getDialogFeatureConfig(applicationId, actionName, feature.name()); if (config != null) { return config.getVersionSpec(); } else { diff --git a/facebook/src/main/java/com/facebook/internal/FetchedAppSettings.java b/facebook/src/main/java/com/facebook/internal/FetchedAppSettings.java new file mode 100644 index 0000000000..8b6728f518 --- /dev/null +++ b/facebook/src/main/java/com/facebook/internal/FetchedAppSettings.java @@ -0,0 +1,238 @@ +/** + * Copyright (c) 2014-present, Facebook, Inc. All rights reserved. + * + * You are hereby granted a non-exclusive, worldwide, royalty-free license to use, + * copy, modify, and distribute this software in source code or binary form for use + * in connection with the web services and APIs provided by Facebook. + * + * As with any software that integrates with the Facebook platform, your use of + * this software is subject to the Facebook Developer Principles and Policies + * [http://developers.facebook.com/policy/]. This copyright notice shall be + * included in all copies or substantial portions of the software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package com.facebook.internal; + +import android.net.Uri; + +import org.json.JSONArray; +import org.json.JSONObject; + +import java.util.EnumSet; +import java.util.Map; + +/** + * com.facebook.internal is solely for the use of other packages within the Facebook SDK for + * Android. Use of any of the classes in this package is unsupported, and they may be modified or + * removed without warning at any time. + */ +public final class FetchedAppSettings { + private boolean supportsImplicitLogging; + private String nuxContent; + private boolean nuxEnabled; + private boolean customTabsEnabled; + private int sessionTimeoutInSeconds; + private EnumSet smartLoginOptions; + private Map> dialogConfigMap; + private boolean automaticLoggingEnabled; + private FacebookRequestErrorClassification errorClassification; + private String smartLoginBookmarkIconURL; + private String smartLoginMenuIconURL; + + public FetchedAppSettings(boolean supportsImplicitLogging, + String nuxContent, + boolean nuxEnabled, + boolean customTabsEnabled, + int sessionTimeoutInSeconds, + EnumSet smartLoginOptions, + Map> dialogConfigMap, + boolean automaticLoggingEnabled, + FacebookRequestErrorClassification errorClassification, + String smartLoginBookmarkIconURL, + String smartLoginMenuIconURL + ) { + this.supportsImplicitLogging = supportsImplicitLogging; + this.nuxContent = nuxContent; + this.nuxEnabled = nuxEnabled; + this.customTabsEnabled = customTabsEnabled; + this.dialogConfigMap = dialogConfigMap; + this.errorClassification = errorClassification; + this.sessionTimeoutInSeconds = sessionTimeoutInSeconds; + this.automaticLoggingEnabled = automaticLoggingEnabled; + this.smartLoginOptions = smartLoginOptions; + this.smartLoginBookmarkIconURL = smartLoginBookmarkIconURL; + this.smartLoginMenuIconURL = smartLoginMenuIconURL; + } + + public boolean supportsImplicitLogging() { + return supportsImplicitLogging; + } + + public String getNuxContent() { + return nuxContent; + } + + public boolean getNuxEnabled() { + return nuxEnabled; + } + + public boolean getCustomTabsEnabled() { + return customTabsEnabled; + } + + public int getSessionTimeoutInSeconds() { + return sessionTimeoutInSeconds; + } + + public boolean getAutomaticLoggingEnabled() { + return automaticLoggingEnabled; + } + + public EnumSet getSmartLoginOptions() { + return smartLoginOptions; + } + + public Map> getDialogConfigurations() { + return dialogConfigMap; + } + + public FacebookRequestErrorClassification getErrorClassification() { + return errorClassification; + } + + public String getSmartLoginBookmarkIconURL() { return smartLoginBookmarkIconURL; } + public String getSmartLoginMenuIconURL() { return smartLoginMenuIconURL; } + + + public static class DialogFeatureConfig { + private static final String DIALOG_CONFIG_DIALOG_NAME_FEATURE_NAME_SEPARATOR = "\\|"; + private static final String DIALOG_CONFIG_NAME_KEY = "name"; + private static final String DIALOG_CONFIG_VERSIONS_KEY = "versions"; + private static final String DIALOG_CONFIG_URL_KEY = "url"; + + public static DialogFeatureConfig parseDialogConfig(JSONObject dialogConfigJSON) { + String dialogNameWithFeature = dialogConfigJSON.optString(DIALOG_CONFIG_NAME_KEY); + if (Utility.isNullOrEmpty(dialogNameWithFeature)) { + return null; + } + + String[] components = dialogNameWithFeature.split( + DIALOG_CONFIG_DIALOG_NAME_FEATURE_NAME_SEPARATOR); + if (components.length != 2) { + // We expect the format to be dialogName|FeatureName, where both components are + // non-empty. + return null; + } + + String dialogName = components[0]; + String featureName = components[1]; + if (Utility.isNullOrEmpty(dialogName) || Utility.isNullOrEmpty(featureName)) { + return null; + } + + String urlString = dialogConfigJSON.optString(DIALOG_CONFIG_URL_KEY); + Uri fallbackUri = null; + if (!Utility.isNullOrEmpty(urlString)) { + fallbackUri = Uri.parse(urlString); + } + + JSONArray versionsJSON = dialogConfigJSON.optJSONArray(DIALOG_CONFIG_VERSIONS_KEY); + + int[] featureVersionSpec = parseVersionSpec(versionsJSON); + + return new DialogFeatureConfig( + dialogName, featureName, fallbackUri, featureVersionSpec); + } + + private static int[] parseVersionSpec(JSONArray versionsJSON) { + // Null signifies no overrides to the min-version as specified by the SDK. + // An empty array would basically turn off the dialog (i.e no supported versions), so + // DON'T default to that. + int[] versionSpec = null; + if (versionsJSON != null) { + int numVersions = versionsJSON.length(); + versionSpec = new int[numVersions]; + for (int i = 0; i < numVersions; i++) { + // See if the version was stored directly as an Integer + int version = versionsJSON.optInt(i, NativeProtocol.NO_PROTOCOL_AVAILABLE); + if (version == NativeProtocol.NO_PROTOCOL_AVAILABLE) { + // If not, then see if it was stored as a string that can be parsed out. + // If even that fails, then we will leave it as NO_PROTOCOL_AVAILABLE + String versionString = versionsJSON.optString(i); + if (!Utility.isNullOrEmpty(versionString)) { + try { + version = Integer.parseInt(versionString); + } catch (NumberFormatException nfe) { + Utility.logd(Utility.LOG_TAG, nfe); + version = NativeProtocol.NO_PROTOCOL_AVAILABLE; + } + } + } + + versionSpec[i] = version; + } + } + + return versionSpec; + } + + private String dialogName; + private String featureName; + private Uri fallbackUrl; + private int[] featureVersionSpec; + + private DialogFeatureConfig( + String dialogName, + String featureName, + Uri fallbackUrl, + int[] featureVersionSpec) { + this.dialogName = dialogName; + this.featureName = featureName; + this.fallbackUrl = fallbackUrl; + this.featureVersionSpec = featureVersionSpec; + } + + public String getDialogName() { + return dialogName; + } + + public String getFeatureName() { + return featureName; + } + + public Uri getFallbackUrl() { + return fallbackUrl; + } + + public int[] getVersionSpec() { + return featureVersionSpec; + } + } + + public static DialogFeatureConfig getDialogFeatureConfig( + String applicationId, + String actionName, + String featureName) { + if (Utility.isNullOrEmpty(actionName) || Utility.isNullOrEmpty(featureName)) { + return null; + } + + FetchedAppSettings settings = FetchedAppSettingsManager. + getAppSettingsWithoutQuery(applicationId); + if (settings != null) { + Map featureMap = + settings.getDialogConfigurations().get(actionName); + if (featureMap != null) { + return featureMap.get(featureName); + } + } + return null; + } +} diff --git a/facebook/src/main/java/com/facebook/internal/FetchedAppSettingsManager.java b/facebook/src/main/java/com/facebook/internal/FetchedAppSettingsManager.java new file mode 100644 index 0000000000..74b77ca3e5 --- /dev/null +++ b/facebook/src/main/java/com/facebook/internal/FetchedAppSettingsManager.java @@ -0,0 +1,237 @@ +/** + * Copyright (c) 2014-present, Facebook, Inc. All rights reserved. + * + * You are hereby granted a non-exclusive, worldwide, royalty-free license to use, + * copy, modify, and distribute this software in source code or binary form for use + * in connection with the web services and APIs provided by Facebook. + * + * As with any software that integrates with the Facebook platform, your use of + * this software is subject to the Facebook Developer Principles and Policies + * [http://developers.facebook.com/policy/]. This copyright notice shall be + * included in all copies or substantial portions of the software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package com.facebook.internal; + +import android.content.Context; +import android.content.SharedPreferences; +import android.os.Bundle; +import android.text.TextUtils; + +import com.facebook.FacebookSdk; +import com.facebook.GraphRequest; +import com.facebook.appevents.internal.Constants; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * com.facebook.internal is solely for the use of other packages within the Facebook SDK for + * Android. Use of any of the classes in this package is unsupported, and they may be modified or + * removed without warning at any time. + */ +public final class FetchedAppSettingsManager { + private static final String APP_SETTINGS_PREFS_STORE = + "com.facebook.internal.preferences.APP_SETTINGS"; + private static final String APP_SETTINGS_PREFS_KEY_FORMAT = + "com.facebook.internal.APP_SETTINGS.%s"; + private static final String APP_SETTING_SUPPORTS_IMPLICIT_SDK_LOGGING = + "supports_implicit_sdk_logging"; + private static final String APP_SETTING_NUX_CONTENT = "gdpv4_nux_content"; + private static final String APP_SETTING_NUX_ENABLED = "gdpv4_nux_enabled"; + private static final String APP_SETTING_CUSTOM_TABS_ENABLED = + "gdpv4_chrome_custom_tabs_enabled"; + private static final String APP_SETTING_DIALOG_CONFIGS = "android_dialog_configs"; + private static final String APP_SETTING_ANDROID_SDK_ERROR_CATEGORIES = + "android_sdk_error_categories"; + private static final String APP_SETTING_APP_EVENTS_SESSION_TIMEOUT = + "app_events_session_timeout"; + private static final String APP_SETTING_APP_EVENTS_FEATURE_BITMASK = + "app_events_feature_bitmask"; + private static final int AUTOMATIC_LOGGING_ENABLED_BITMASK_FIELD = 1 << 3; + private static final String APP_SETTING_SMART_LOGIN_OPTIONS = + "seamless_login"; + private static final String SMART_LOGIN_BOOKMARK_ICON_URL = "smart_login_bookmark_icon_url"; + private static final String SMART_LOGIN_MENU_ICON_URL = "smart_login_menu_icon_url"; + + private static final String[] APP_SETTING_FIELDS = new String[]{ + APP_SETTING_SUPPORTS_IMPLICIT_SDK_LOGGING, + APP_SETTING_NUX_CONTENT, + APP_SETTING_NUX_ENABLED, + APP_SETTING_CUSTOM_TABS_ENABLED, + APP_SETTING_DIALOG_CONFIGS, + APP_SETTING_ANDROID_SDK_ERROR_CATEGORIES, + APP_SETTING_APP_EVENTS_SESSION_TIMEOUT, + APP_SETTING_APP_EVENTS_FEATURE_BITMASK, + APP_SETTING_SMART_LOGIN_OPTIONS, + SMART_LOGIN_BOOKMARK_ICON_URL, + SMART_LOGIN_MENU_ICON_URL, + }; + private static final String APPLICATION_FIELDS = "fields"; + + private static Map fetchedAppSettings = + new ConcurrentHashMap(); + private static AtomicBoolean loadingSettings = new AtomicBoolean(false); + + public static void loadAppSettingsAsync( + final Context context, + final String applicationId + ) { + boolean canStartLoading = loadingSettings.compareAndSet(false, true); + if (Utility.isNullOrEmpty(applicationId) || + fetchedAppSettings.containsKey(applicationId) || + !canStartLoading) { + return; + } + + final String settingsKey = String.format(APP_SETTINGS_PREFS_KEY_FORMAT, applicationId); + + FacebookSdk.getExecutor().execute(new Runnable() { + @Override + public void run() { + // See if we had a cached copy and use that immediately. + SharedPreferences sharedPrefs = context.getSharedPreferences( + APP_SETTINGS_PREFS_STORE, + Context.MODE_PRIVATE); + String settingsJSONString = sharedPrefs.getString(settingsKey, null); + if (!Utility.isNullOrEmpty(settingsJSONString)) { + JSONObject settingsJSON = null; + try { + settingsJSON = new JSONObject(settingsJSONString); + } catch (JSONException je) { + Utility.logd(Utility.LOG_TAG, je); + } + if (settingsJSON != null) { + parseAppSettingsFromJSON(applicationId, settingsJSON); + } + } + + JSONObject resultJSON = getAppSettingsQueryResponse(applicationId); + if (resultJSON != null) { + parseAppSettingsFromJSON(applicationId, resultJSON); + + sharedPrefs.edit() + .putString(settingsKey, resultJSON.toString()) + .apply(); + } + + loadingSettings.set(false); + } + }); + } + + // This call only gets the app settings if they're already fetched + public static FetchedAppSettings getAppSettingsWithoutQuery(final String applicationId) { + return applicationId != null ? fetchedAppSettings.get(applicationId) : null; + } + + // Note that this method makes a synchronous Graph API call, so should not be called from the + // main thread. + public static FetchedAppSettings queryAppSettings( + final String applicationId, + final boolean forceRequery) { + // Cache the last app checked results. + if (!forceRequery && fetchedAppSettings.containsKey(applicationId)) { + return fetchedAppSettings.get(applicationId); + } + + JSONObject response = getAppSettingsQueryResponse(applicationId); + if (response == null) { + return null; + } + + return parseAppSettingsFromJSON(applicationId, response); + } + + private static FetchedAppSettings parseAppSettingsFromJSON( + String applicationId, + JSONObject settingsJSON) { + JSONArray errorClassificationJSON = + settingsJSON.optJSONArray(APP_SETTING_ANDROID_SDK_ERROR_CATEGORIES); + FacebookRequestErrorClassification errorClassification = + errorClassificationJSON == null + ? FacebookRequestErrorClassification.getDefaultErrorClassification() + : FacebookRequestErrorClassification.createFromJSON( + errorClassificationJSON + ); + int featureBitmask = settingsJSON.optInt(APP_SETTING_APP_EVENTS_FEATURE_BITMASK,0); + boolean automaticLoggingEnabled = + (featureBitmask & AUTOMATIC_LOGGING_ENABLED_BITMASK_FIELD) != 0; + FetchedAppSettings result = new FetchedAppSettings( + settingsJSON.optBoolean(APP_SETTING_SUPPORTS_IMPLICIT_SDK_LOGGING, false), + settingsJSON.optString(APP_SETTING_NUX_CONTENT, ""), + settingsJSON.optBoolean(APP_SETTING_NUX_ENABLED, false), + settingsJSON.optBoolean(APP_SETTING_CUSTOM_TABS_ENABLED, false), + settingsJSON.optInt( + APP_SETTING_APP_EVENTS_SESSION_TIMEOUT, + Constants.getDefaultAppEventsSessionTimeoutInSeconds()), + SmartLoginOption.parseOptions(settingsJSON.optLong(APP_SETTING_SMART_LOGIN_OPTIONS)), + parseDialogConfigurations(settingsJSON.optJSONObject(APP_SETTING_DIALOG_CONFIGS)), + automaticLoggingEnabled, + errorClassification, + settingsJSON.optString(SMART_LOGIN_BOOKMARK_ICON_URL), + settingsJSON.optString(SMART_LOGIN_MENU_ICON_URL) + ); + + fetchedAppSettings.put(applicationId, result); + + return result; + } + + // Note that this method makes a synchronous Graph API call, so should not be called from the + // main thread. + private static JSONObject getAppSettingsQueryResponse(String applicationId) { + Bundle appSettingsParams = new Bundle(); + appSettingsParams.putString(APPLICATION_FIELDS, TextUtils.join(",", APP_SETTING_FIELDS)); + + GraphRequest request = GraphRequest.newGraphPathRequest(null, applicationId, null); + request.setSkipClientToken(true); + request.setParameters(appSettingsParams); + + return request.executeAndWait().getJSONObject(); + } + + private static Map> parseDialogConfigurations( + JSONObject dialogConfigResponse) { + HashMap> dialogConfigMap + = new HashMap>(); + + if (dialogConfigResponse != null) { + JSONArray dialogConfigData = dialogConfigResponse.optJSONArray("data"); + if (dialogConfigData != null) { + for (int i = 0; i < dialogConfigData.length(); i++) { + FetchedAppSettings.DialogFeatureConfig dialogConfig = + FetchedAppSettings.DialogFeatureConfig.parseDialogConfig( + dialogConfigData.optJSONObject(i)); + if (dialogConfig == null) { + continue; + } + + String dialogName = dialogConfig.getDialogName(); + Map featureMap = + dialogConfigMap.get(dialogName); + if (featureMap == null) { + featureMap = new HashMap(); + dialogConfigMap.put(dialogName, featureMap); + } + featureMap.put(dialogConfig.getFeatureName(), dialogConfig); + } + } + } + + return dialogConfigMap; + } +} diff --git a/facebook/src/main/java/com/facebook/internal/FileLruCache.java b/facebook/src/main/java/com/facebook/internal/FileLruCache.java index 72e73d467a..fcbc1c5010 100644 --- a/facebook/src/main/java/com/facebook/internal/FileLruCache.java +++ b/facebook/src/main/java/com/facebook/internal/FileLruCache.java @@ -127,7 +127,7 @@ public InputStream get(String key) throws IOException { public InputStream get(String key, String contentTag) throws IOException { File file = new File(this.directory, Utility.md5hash(key)); - FileInputStream input = null; + FileInputStream input; try { input = new FileInputStream(file); } catch (IOException e) { @@ -184,7 +184,7 @@ public OutputStream openPutStream(final String key, String contentTag) throws IO throw new IOException("Could not create file at " + buffer.getAbsolutePath()); } - FileOutputStream file = null; + FileOutputStream file; try { file = new FileOutputStream(buffer); } catch (FileNotFoundException e) { @@ -448,7 +448,7 @@ static JSONObject readHeader(InputStream stream) throws IOException { } String headerString = new String(headerBytes); - JSONObject header = null; + JSONObject header; JSONTokener tokener = new JSONTokener(headerString); try { Object parsed = tokener.nextValue(); diff --git a/facebook/src/main/java/com/facebook/internal/GraphUtil.java b/facebook/src/main/java/com/facebook/internal/GraphUtil.java index 93b4566ed8..ea60a38d18 100644 --- a/facebook/src/main/java/com/facebook/internal/GraphUtil.java +++ b/facebook/src/main/java/com/facebook/internal/GraphUtil.java @@ -22,17 +22,11 @@ import com.facebook.FacebookException; import com.facebook.internal.NativeProtocol; -import com.facebook.internal.Validate; + import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; -import java.text.ParseException; -import java.text.SimpleDateFormat; -import java.util.Date; -import java.util.List; -import java.util.Locale; - /** * com.facebook.internal is solely for the use of other packages within the Facebook SDK for * Android. Use of any of the classes in this package is unsupported, and they may be modified or diff --git a/facebook/src/main/java/com/facebook/internal/ImageDownloader.java b/facebook/src/main/java/com/facebook/internal/ImageDownloader.java index 9309f2beb6..14b3089202 100644 --- a/facebook/src/main/java/com/facebook/internal/ImageDownloader.java +++ b/facebook/src/main/java/com/facebook/internal/ImageDownloader.java @@ -33,8 +33,6 @@ import java.io.InputStream; import java.io.InputStreamReader; import java.net.HttpURLConnection; -import java.net.URI; -import java.net.URISyntaxException; import java.net.URL; import java.util.HashMap; import java.util.Map; diff --git a/facebook/src/main/java/com/facebook/internal/ImageRequest.java b/facebook/src/main/java/com/facebook/internal/ImageRequest.java index f0dbfbf3e8..acbe35ebb3 100644 --- a/facebook/src/main/java/com/facebook/internal/ImageRequest.java +++ b/facebook/src/main/java/com/facebook/internal/ImageRequest.java @@ -23,8 +23,6 @@ import android.content.Context; import android.net.Uri; -import java.net.URI; -import java.net.URISyntaxException; import java.util.Locale; /** diff --git a/facebook/src/main/java/com/facebook/internal/ImageResponseCache.java b/facebook/src/main/java/com/facebook/internal/ImageResponseCache.java index 847c441d42..0a4b47583c 100644 --- a/facebook/src/main/java/com/facebook/internal/ImageResponseCache.java +++ b/facebook/src/main/java/com/facebook/internal/ImageResponseCache.java @@ -29,9 +29,6 @@ import java.io.IOException; import java.io.InputStream; import java.net.HttpURLConnection; -import java.net.URI; -import java.net.URISyntaxException; -import java.net.URL; /** * com.facebook.internal is solely for the use of other packages within the diff --git a/facebook/src/main/java/com/facebook/internal/JsonUtil.java b/facebook/src/main/java/com/facebook/internal/JsonUtil.java index 021f9a7ef8..b009e11b26 100644 --- a/facebook/src/main/java/com/facebook/internal/JsonUtil.java +++ b/facebook/src/main/java/com/facebook/internal/JsonUtil.java @@ -34,7 +34,7 @@ class JsonUtil { static void jsonObjectClear(JSONObject jsonObject) { @SuppressWarnings("unchecked") - Iterator keys = (Iterator) jsonObject.keys(); + Iterator keys = jsonObject.keys(); while (keys.hasNext()) { keys.next(); keys.remove(); @@ -43,7 +43,7 @@ static void jsonObjectClear(JSONObject jsonObject) { static boolean jsonObjectContainsValue(JSONObject jsonObject, Object value) { @SuppressWarnings("unchecked") - Iterator keys = (Iterator) jsonObject.keys(); + Iterator keys = jsonObject.keys(); while (keys.hasNext()) { Object thisValue = jsonObject.opt(keys.next()); if (thisValue != null && thisValue.equals(value)) { @@ -84,7 +84,7 @@ static Set> jsonObjectEntrySet(JSONObject jsonObject) HashSet> result = new HashSet>(); @SuppressWarnings("unchecked") - Iterator keys = (Iterator) jsonObject.keys(); + Iterator keys = jsonObject.keys(); while (keys.hasNext()) { String key = keys.next(); Object value = jsonObject.opt(key); @@ -98,7 +98,7 @@ static Set jsonObjectKeySet(JSONObject jsonObject) { HashSet result = new HashSet(); @SuppressWarnings("unchecked") - Iterator keys = (Iterator) jsonObject.keys(); + Iterator keys = jsonObject.keys(); while (keys.hasNext()) { result.add(keys.next()); } @@ -121,7 +121,7 @@ static Collection jsonObjectValues(JSONObject jsonObject) { ArrayList result = new ArrayList(); @SuppressWarnings("unchecked") - Iterator keys = (Iterator) jsonObject.keys(); + Iterator keys = jsonObject.keys(); while (keys.hasNext()) { result.add(jsonObject.opt(keys.next())); } diff --git a/facebook/src/main/java/com/facebook/internal/NativeAppCallAttachmentStore.java b/facebook/src/main/java/com/facebook/internal/NativeAppCallAttachmentStore.java index 18814f3550..e3a44572f0 100644 --- a/facebook/src/main/java/com/facebook/internal/NativeAppCallAttachmentStore.java +++ b/facebook/src/main/java/com/facebook/internal/NativeAppCallAttachmentStore.java @@ -81,7 +81,7 @@ private static void processAttachmentFile( File outputFile) throws IOException { FileOutputStream outputStream = new FileOutputStream(outputFile); try { - InputStream inputStream = null; + InputStream inputStream; if (!isContentUri) { inputStream = new FileInputStream(imageUri.getPath()); } else { diff --git a/facebook/src/main/java/com/facebook/internal/NativeProtocol.java b/facebook/src/main/java/com/facebook/internal/NativeProtocol.java index 610acea347..4750fbbdf7 100644 --- a/facebook/src/main/java/com/facebook/internal/NativeProtocol.java +++ b/facebook/src/main/java/com/facebook/internal/NativeProtocol.java @@ -23,7 +23,12 @@ import android.content.ContentResolver; import android.content.Context; import android.content.Intent; -import android.content.pm.*; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.pm.ProviderInfo; +import android.content.pm.ResolveInfo; +import android.content.pm.Signature; import android.database.Cursor; import android.net.Uri; import android.os.Build; @@ -36,12 +41,17 @@ import com.facebook.FacebookSdk; import com.facebook.login.DefaultAudience; -import java.util.*; -import java.util.concurrent.Callable; -import java.util.concurrent.Executors; -import java.util.concurrent.FutureTask; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.TreeSet; +import java.util.UUID; import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicInteger; /** * com.facebook.internal is solely for the use of other packages within the Facebook SDK for @@ -257,7 +267,7 @@ public boolean validateSignature(Context context, String packageName) { return true; } - PackageInfo packageInfo = null; + PackageInfo packageInfo; try { packageInfo = context.getPackageManager().getPackageInfo(packageName, PackageManager.GET_SIGNATURES); diff --git a/facebook/src/main/java/com/facebook/internal/SmartLoginOption.java b/facebook/src/main/java/com/facebook/internal/SmartLoginOption.java new file mode 100644 index 0000000000..4283bda91b --- /dev/null +++ b/facebook/src/main/java/com/facebook/internal/SmartLoginOption.java @@ -0,0 +1,49 @@ +/** + * Copyright (c) 2014-present, Facebook, Inc. All rights reserved. + * + * You are hereby granted a non-exclusive, worldwide, royalty-free license to use, + * copy, modify, and distribute this software in source code or binary form for use + * in connection with the web services and APIs provided by Facebook. + * + * As with any software that integrates with the Facebook platform, your use of + * this software is subject to the Facebook Developer Principles and Policies + * [http://developers.facebook.com/policy/]. This copyright notice shall be + * included in all copies or substantial portions of the software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.facebook.internal; + +import java.util.EnumSet; + +public enum SmartLoginOption { + None(0), + Enabled(1), + RequireConfirm(2); + + public static final EnumSet ALL = EnumSet.allOf(SmartLoginOption.class); + public static EnumSet parseOptions(long bitmask) { + EnumSet result = EnumSet.noneOf(SmartLoginOption.class); + for (SmartLoginOption opt : ALL) { + if ((bitmask & opt.getValue()) != 0) { + result.add(opt); + } + } + return result; + } + + private final long mValue; + + SmartLoginOption(long value) { + this.mValue= value; + } + + public long getValue(){ + return mValue; + } +} diff --git a/facebook/src/main/java/com/facebook/internal/Utility.java b/facebook/src/main/java/com/facebook/internal/Utility.java index 7c1411a1ba..fcf3fdc825 100644 --- a/facebook/src/main/java/com/facebook/internal/Utility.java +++ b/facebook/src/main/java/com/facebook/internal/Utility.java @@ -21,7 +21,6 @@ package com.facebook.internal; import android.content.Context; -import android.content.SharedPreferences; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.database.Cursor; @@ -47,25 +46,42 @@ import com.facebook.GraphRequest; import com.facebook.GraphResponse; import com.facebook.HttpMethod; -import com.facebook.appevents.AppEventsConstants; -import com.facebook.appevents.internal.Constants; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import org.json.JSONTokener; -import java.io.*; +import java.io.BufferedInputStream; +import java.io.Closeable; +import java.io.File; +import java.io.FilenameFilter; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.math.BigInteger; import java.net.HttpURLConnection; import java.net.URLConnection; - import java.net.URLDecoder; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Random; +import java.util.Set; +import java.util.TimeZone; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicBoolean; import java.util.regex.Pattern; @@ -80,44 +96,10 @@ public final class Utility { private static final String HASH_ALGORITHM_MD5 = "MD5"; private static final String HASH_ALGORITHM_SHA1 = "SHA-1"; private static final String URL_SCHEME = "https"; - private static final String APP_SETTINGS_PREFS_STORE = - "com.facebook.internal.preferences.APP_SETTINGS"; - private static final String APP_SETTINGS_PREFS_KEY_FORMAT = - "com.facebook.internal.APP_SETTINGS.%s"; - private static final String APP_SETTING_SUPPORTS_IMPLICIT_SDK_LOGGING = - "supports_implicit_sdk_logging"; - private static final String APP_SETTING_NUX_CONTENT = "gdpv4_nux_content"; - private static final String APP_SETTING_NUX_ENABLED = "gdpv4_nux_enabled"; - private static final String APP_SETTING_CUSTOM_TABS_ENABLED = - "gdpv4_chrome_custom_tabs_enabled"; - private static final String APP_SETTING_DIALOG_CONFIGS = "android_dialog_configs"; - private static final String APP_SETTING_ANDROID_SDK_ERROR_CATEGORIES = - "android_sdk_error_categories"; - private static final String APP_SETTING_APP_EVENTS_SESSION_TIMEOUT = - "app_events_session_timeout"; - private static final String APP_SETTING_APP_EVENTS_FEATURE_BITMASK = - "app_events_feature_bitmask"; - private static final int AUTOMATIC_LOGGING_ENABLED_BITMASK_FIELD = 1 << 3; private static final String EXTRA_APP_EVENTS_INFO_FORMAT_VERSION = "a2"; - private static final String DIALOG_CONFIG_DIALOG_NAME_FEATURE_NAME_SEPARATOR = "\\|"; - private static final String DIALOG_CONFIG_NAME_KEY = "name"; - private static final String DIALOG_CONFIG_VERSIONS_KEY = "versions"; - private static final String DIALOG_CONFIG_URL_KEY = "url"; private final static String UTF8 = "UTF-8"; - private static final String[] APP_SETTING_FIELDS = new String[]{ - APP_SETTING_SUPPORTS_IMPLICIT_SDK_LOGGING, - APP_SETTING_NUX_CONTENT, - APP_SETTING_NUX_ENABLED, - APP_SETTING_CUSTOM_TABS_ENABLED, - APP_SETTING_DIALOG_CONFIGS, - APP_SETTING_ANDROID_SDK_ERROR_CATEGORIES, - APP_SETTING_APP_EVENTS_SESSION_TIMEOUT, - APP_SETTING_APP_EVENTS_FEATURE_BITMASK - }; - private static final String APPLICATION_FIELDS = "fields"; - // This is the default used by the buffer streams, but they trace a warning if you do not // specify. public static final int DEFAULT_STREAM_BUFFER_SIZE = 8192; @@ -129,11 +111,6 @@ public final class Utility { private static final int GINGERBREAD_MR1 = 10; - private static Map fetchedAppSettings = - new ConcurrentHashMap(); - - private static AtomicBoolean loadingSettings = new AtomicBoolean(false); - private static int numCPUCores = 0; private static long timestampOfLastCheck = -1; @@ -143,167 +120,6 @@ public final class Utility { private static String deviceTimeZoneName = ""; private static String carrierName = noCarrierConstant; - public static class FetchedAppSettings { - private boolean supportsImplicitLogging; - private String nuxContent; - private boolean nuxEnabled; - private boolean customTabsEnabled; - private int sessionTimeoutInSeconds; - private Map> dialogConfigMap; - private boolean automaticLoggingEnabled; - private FacebookRequestErrorClassification errorClassification; - - private FetchedAppSettings(boolean supportsImplicitLogging, - String nuxContent, - boolean nuxEnabled, - boolean customTabsEnabled, - int sessionTimeoutInSeconds, - Map> dialogConfigMap, - boolean automaticLoggingEnabled, - FacebookRequestErrorClassification errorClassification) { - this.supportsImplicitLogging = supportsImplicitLogging; - this.nuxContent = nuxContent; - this.nuxEnabled = nuxEnabled; - this.customTabsEnabled = customTabsEnabled; - this.dialogConfigMap = dialogConfigMap; - this.errorClassification = errorClassification; - this.sessionTimeoutInSeconds = sessionTimeoutInSeconds; - this.automaticLoggingEnabled = automaticLoggingEnabled; - } - - public boolean supportsImplicitLogging() { - return supportsImplicitLogging; - } - - public String getNuxContent() { - return nuxContent; - } - - public boolean getNuxEnabled() { - return nuxEnabled; - } - - public boolean getCustomTabsEnabled() { - return customTabsEnabled; - } - - public int getSessionTimeoutInSeconds() { - return sessionTimeoutInSeconds; - } - - public boolean getAutomaticLoggingEnabled() { - return automaticLoggingEnabled; - } - - public Map> getDialogConfigurations() { - return dialogConfigMap; - } - - public FacebookRequestErrorClassification getErrorClassification() { - return errorClassification; - } - } - - public static class DialogFeatureConfig { - private static DialogFeatureConfig parseDialogConfig(JSONObject dialogConfigJSON) { - String dialogNameWithFeature = dialogConfigJSON.optString(DIALOG_CONFIG_NAME_KEY); - if (Utility.isNullOrEmpty(dialogNameWithFeature)) { - return null; - } - - String[] components = dialogNameWithFeature.split( - DIALOG_CONFIG_DIALOG_NAME_FEATURE_NAME_SEPARATOR); - if (components.length != 2) { - // We expect the format to be dialogName|FeatureName, where both components are - // non-empty. - return null; - } - - String dialogName = components[0]; - String featureName = components[1]; - if (isNullOrEmpty(dialogName) || isNullOrEmpty(featureName)) { - return null; - } - - String urlString = dialogConfigJSON.optString(DIALOG_CONFIG_URL_KEY); - Uri fallbackUri = null; - if (!Utility.isNullOrEmpty(urlString)) { - fallbackUri = Uri.parse(urlString); - } - - JSONArray versionsJSON = dialogConfigJSON.optJSONArray(DIALOG_CONFIG_VERSIONS_KEY); - - int[] featureVersionSpec = parseVersionSpec(versionsJSON); - - return new DialogFeatureConfig( - dialogName, featureName, fallbackUri, featureVersionSpec); - } - - private static int[] parseVersionSpec(JSONArray versionsJSON) { - // Null signifies no overrides to the min-version as specified by the SDK. - // An empty array would basically turn off the dialog (i.e no supported versions), so - // DON'T default to that. - int[] versionSpec = null; - if (versionsJSON != null) { - int numVersions = versionsJSON.length(); - versionSpec = new int[numVersions]; - for (int i = 0; i < numVersions; i++) { - // See if the version was stored directly as an Integer - int version = versionsJSON.optInt(i, NativeProtocol.NO_PROTOCOL_AVAILABLE); - if (version == NativeProtocol.NO_PROTOCOL_AVAILABLE) { - // If not, then see if it was stored as a string that can be parsed out. - // If even that fails, then we will leave it as NO_PROTOCOL_AVAILABLE - String versionString = versionsJSON.optString(i); - if (!isNullOrEmpty(versionString)) { - try { - version = Integer.parseInt(versionString); - } catch (NumberFormatException nfe) { - logd(LOG_TAG, nfe); - version = NativeProtocol.NO_PROTOCOL_AVAILABLE; - } - } - } - - versionSpec[i] = version; - } - } - - return versionSpec; - } - - private String dialogName; - private String featureName; - private Uri fallbackUrl; - private int[] featureVersionSpec; - - private DialogFeatureConfig( - String dialogName, - String featureName, - Uri fallbackUrl, - int[] featureVersionSpec) { - this.dialogName = dialogName; - this.featureName = featureName; - this.fallbackUrl = fallbackUrl; - this.featureVersionSpec = featureVersionSpec; - } - - public String getDialogName() { - return dialogName; - } - - public String getFeatureName() { - return featureName; - } - - public Uri getFallbackUrl() { - return fallbackUrl; - } - - public int[] getVersionSpec() { - return featureVersionSpec; - } - } - /** * Each array represents a set of closed or open Range, like so: [0,10,50,60] - Ranges are * {0-9}, {50-59} [20] - Ranges are {20-} [30,40,100] - Ranges are {30-39}, {100-} @@ -576,9 +392,9 @@ public static boolean putJSONValueInBundle(Bundle bundle, String key, Object val } else if (value instanceof String) { bundle.putString(key, (String) value); } else if (value instanceof JSONArray) { - bundle.putString(key, ((JSONArray) value).toString()); + bundle.putString(key, value.toString()); } else if (value instanceof JSONObject) { - bundle.putString(key, ((JSONObject) value).toString()); + bundle.putString(key, value.toString()); } else { return false; } @@ -796,168 +612,6 @@ public static boolean hasSameId(JSONObject a, JSONObject b) { return idA.equals(idB); } - public static void loadAppSettingsAsync( - final Context context, - final String applicationId - ) { - boolean canStartLoading = loadingSettings.compareAndSet(false, true); - if (Utility.isNullOrEmpty(applicationId) || - fetchedAppSettings.containsKey(applicationId) || - !canStartLoading) { - return; - } - - final String settingsKey = String.format(APP_SETTINGS_PREFS_KEY_FORMAT, applicationId); - - FacebookSdk.getExecutor().execute(new Runnable() { - @Override - public void run() { - // See if we had a cached copy and use that immediately. - SharedPreferences sharedPrefs = context.getSharedPreferences( - APP_SETTINGS_PREFS_STORE, - Context.MODE_PRIVATE); - String settingsJSONString = sharedPrefs.getString(settingsKey, null); - if (!isNullOrEmpty(settingsJSONString)) { - JSONObject settingsJSON = null; - try { - settingsJSON = new JSONObject(settingsJSONString); - } catch (JSONException je) { - logd(LOG_TAG, je); - } - if (settingsJSON != null) { - parseAppSettingsFromJSON(applicationId, settingsJSON); - } - } - - JSONObject resultJSON = getAppSettingsQueryResponse(applicationId); - if (resultJSON != null) { - parseAppSettingsFromJSON(applicationId, resultJSON); - - sharedPrefs.edit() - .putString(settingsKey, resultJSON.toString()) - .apply(); - } - - loadingSettings.set(false); - } - }); - } - - // This call only gets the app settings if they're already fetched - public static FetchedAppSettings getAppSettingsWithoutQuery(final String applicationId) { - return applicationId != null ? fetchedAppSettings.get(applicationId) : null; - } - - // Note that this method makes a synchronous Graph API call, so should not be called from the - // main thread. - public static FetchedAppSettings queryAppSettings( - final String applicationId, - final boolean forceRequery) { - // Cache the last app checked results. - if (!forceRequery && fetchedAppSettings.containsKey(applicationId)) { - return fetchedAppSettings.get(applicationId); - } - - JSONObject response = getAppSettingsQueryResponse(applicationId); - if (response == null) { - return null; - } - - return parseAppSettingsFromJSON(applicationId, response); - } - - private static FetchedAppSettings parseAppSettingsFromJSON( - String applicationId, - JSONObject settingsJSON) { - JSONArray errorClassificationJSON = - settingsJSON.optJSONArray(APP_SETTING_ANDROID_SDK_ERROR_CATEGORIES); - FacebookRequestErrorClassification errorClassification = - errorClassificationJSON == null - ? FacebookRequestErrorClassification.getDefaultErrorClassification() - : FacebookRequestErrorClassification.createFromJSON( - errorClassificationJSON - ); - int featureBitmask = settingsJSON.optInt(APP_SETTING_APP_EVENTS_FEATURE_BITMASK,0); - boolean automaticLoggingEnabled = - (featureBitmask & AUTOMATIC_LOGGING_ENABLED_BITMASK_FIELD) != 0; - FetchedAppSettings result = new FetchedAppSettings( - settingsJSON.optBoolean(APP_SETTING_SUPPORTS_IMPLICIT_SDK_LOGGING, false), - settingsJSON.optString(APP_SETTING_NUX_CONTENT, ""), - settingsJSON.optBoolean(APP_SETTING_NUX_ENABLED, false), - settingsJSON.optBoolean(APP_SETTING_CUSTOM_TABS_ENABLED, false), - settingsJSON.optInt( - APP_SETTING_APP_EVENTS_SESSION_TIMEOUT, - Constants.getDefaultAppEventsSessionTimeoutInSeconds()), - parseDialogConfigurations(settingsJSON.optJSONObject(APP_SETTING_DIALOG_CONFIGS)), - automaticLoggingEnabled, - errorClassification - ); - - fetchedAppSettings.put(applicationId, result); - - return result; - } - - // Note that this method makes a synchronous Graph API call, so should not be called from the - // main thread. - private static JSONObject getAppSettingsQueryResponse(String applicationId) { - Bundle appSettingsParams = new Bundle(); - appSettingsParams.putString(APPLICATION_FIELDS, TextUtils.join(",", APP_SETTING_FIELDS)); - - GraphRequest request = GraphRequest.newGraphPathRequest(null, applicationId, null); - request.setSkipClientToken(true); - request.setParameters(appSettingsParams); - - return request.executeAndWait().getJSONObject(); - } - - public static DialogFeatureConfig getDialogFeatureConfig( - String applicationId, - String actionName, - String featureName) { - if (Utility.isNullOrEmpty(actionName) || Utility.isNullOrEmpty(featureName)) { - return null; - } - - FetchedAppSettings settings = fetchedAppSettings.get(applicationId); - if (settings != null) { - Map featureMap = - settings.getDialogConfigurations().get(actionName); - if (featureMap != null) { - return featureMap.get(featureName); - } - } - return null; - } - - private static Map> parseDialogConfigurations( - JSONObject dialogConfigResponse) { - HashMap> dialogConfigMap = new HashMap>(); - - if (dialogConfigResponse != null) { - JSONArray dialogConfigData = dialogConfigResponse.optJSONArray("data"); - if (dialogConfigData != null) { - for (int i = 0; i < dialogConfigData.length(); i++) { - DialogFeatureConfig dialogConfig = DialogFeatureConfig.parseDialogConfig( - dialogConfigData.optJSONObject(i)); - if (dialogConfig == null) { - continue; - } - - String dialogName = dialogConfig.getDialogName(); - Map featureMap = dialogConfigMap.get(dialogName); - if (featureMap == null) { - featureMap = new HashMap(); - dialogConfigMap.put(dialogName, featureMap); - } - featureMap.put(dialogConfig.getFeatureName(), dialogConfig); - } - } - } - - return dialogConfigMap; - } - public static String safeGetStringFromResponse(JSONObject response, String propertyName) { return response != null ? response.optString(propertyName, "") : ""; } @@ -1075,7 +729,7 @@ public static void setAppEventExtendedDeviceInfoParameters( extraInfoArray.put(Build.MODEL); // Locale - Locale locale = null; + Locale locale; try { locale = appContext.getResources().getConfiguration().locale; } catch (Exception e) { @@ -1246,7 +900,7 @@ public static Date getBundleLongAsDate(Bundle bundle, String key, Date dateBase) return null; } - long secondsFromBase = Long.MIN_VALUE; + long secondsFromBase; Object secondsObject = bundle.get(key); if (secondsObject instanceof Long) { @@ -1372,7 +1026,6 @@ private static int refreshBestGuessNumberOfCPUCores() { // Enumerate all available CPU files and try to count the number of CPU cores. try { - int res = 0; File cpuDir = new File("/sys/devices/system/cpu/"); File[] cpuFiles = cpuDir.listFiles(new FilenameFilter() { @Override diff --git a/facebook/src/main/java/com/facebook/internal/WebDialog.java b/facebook/src/main/java/com/facebook/internal/WebDialog.java index 528d55d6ec..03fce88591 100644 --- a/facebook/src/main/java/com/facebook/internal/WebDialog.java +++ b/facebook/src/main/java/com/facebook/internal/WebDialog.java @@ -399,7 +399,7 @@ protected void sendSuccessToListener(Bundle values) { protected void sendErrorToListener(Throwable error) { if (onCompleteListener != null && !listenerCalled) { listenerCalled = true; - FacebookException facebookException = null; + FacebookException facebookException; if (error instanceof FacebookException) { facebookException = (FacebookException) error; } else { diff --git a/facebook/src/main/java/com/facebook/login/CustomTabLoginMethodHandler.java b/facebook/src/main/java/com/facebook/login/CustomTabLoginMethodHandler.java index 9298f6de5b..06c5b33c45 100644 --- a/facebook/src/main/java/com/facebook/login/CustomTabLoginMethodHandler.java +++ b/facebook/src/main/java/com/facebook/login/CustomTabLoginMethodHandler.java @@ -37,6 +37,8 @@ import com.facebook.FacebookRequestError; import com.facebook.FacebookSdk; import com.facebook.FacebookServiceException; +import com.facebook.internal.FetchedAppSettings; +import com.facebook.internal.FetchedAppSettingsManager; import com.facebook.internal.ServerProtocol; import com.facebook.internal.Utility; import com.facebook.internal.Validate; @@ -110,7 +112,7 @@ && getChromePackage() != null private boolean isCustomTabsEnabled() { final String appId = Utility.getMetadataApplicationId(loginClient.getActivity()); - final Utility.FetchedAppSettings settings = Utility.getAppSettingsWithoutQuery(appId); + final FetchedAppSettings settings = FetchedAppSettingsManager.getAppSettingsWithoutQuery(appId); return settings != null && settings.getCustomTabsEnabled(); } diff --git a/facebook/src/main/java/com/facebook/login/DeviceAuthDialog.java b/facebook/src/main/java/com/facebook/login/DeviceAuthDialog.java index b6f8c66d9f..f81687eba7 100644 --- a/facebook/src/main/java/com/facebook/login/DeviceAuthDialog.java +++ b/facebook/src/main/java/com/facebook/login/DeviceAuthDialog.java @@ -20,8 +20,12 @@ package com.facebook.login; +import android.app.AlertDialog; import android.app.Dialog; import android.content.DialogInterface; +import android.graphics.Bitmap; +import android.graphics.drawable.BitmapDrawable; +import android.net.Uri; import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; @@ -48,6 +52,15 @@ import com.facebook.GraphResponse; import com.facebook.HttpMethod; import com.facebook.R; +import com.facebook.appevents.AppEventsLogger; +import com.facebook.devicerequests.internal.DeviceRequestsHelper; +import com.facebook.internal.AnalyticsEvents; +import com.facebook.internal.FetchedAppSettings; +import com.facebook.internal.FetchedAppSettingsManager; +import com.facebook.internal.ImageDownloader; +import com.facebook.internal.ImageRequest; +import com.facebook.internal.ImageResponse; +import com.facebook.internal.SmartLoginOption; import com.facebook.internal.Utility; import com.facebook.internal.Validate; @@ -81,6 +94,8 @@ public class DeviceAuthDialog extends DialogFragment { // Used to tell if we are destroying the fragment because it was dismissed or dismissing the // fragment because it is being destroyed. private boolean isBeingDestroyed = false; + private boolean isRetry = false; + private LoginClient.Request mRequest = null; @Nullable @Override @@ -110,22 +125,8 @@ public View onCreateView( public Dialog onCreateDialog(Bundle savedInstanceState) { dialog = new Dialog(getActivity(), R.style.com_facebook_auth_dialog); LayoutInflater inflater = getActivity().getLayoutInflater(); - View view = inflater.inflate(R.layout.com_facebook_device_auth_dialog_fragment, null); - progressBar = (ProgressBar)view.findViewById(R.id.progress_bar); - confirmationCode = (TextView)view.findViewById(R.id.confirmation_code); - - Button cancelButton = (Button) view.findViewById(R.id.cancel_button); - cancelButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - onCancel(); - } - }); - TextView instructions = (TextView)view.findViewById( - R.id.com_facebook_device_auth_instructions); - instructions.setText( - Html.fromHtml(getString(R.string.com_facebook_device_auth_instructions))); + View view = initializeContentView(DeviceRequestsHelper.isAvailable() && !this.isRetry); dialog.setContentView(view); return dialog; @@ -165,6 +166,7 @@ public void onDestroy() { } public void startLogin(final LoginClient.Request request) { + this.mRequest = request; Bundle parameters = new Bundle(); parameters.putString("scope", TextUtils.join(",", request.getPermissions())); @@ -175,6 +177,9 @@ public void startLogin(final LoginClient.Request request) { String accessToken = Validate.hasAppID()+ "|" + Validate.hasClientToken(); parameters.putString(GraphRequest.ACCESS_TOKEN_PARAM, accessToken); + parameters.putString(DeviceRequestsHelper.DEVICE_INFO_PARAM, + DeviceRequestsHelper.getDeviceInfo()); + GraphRequest graphRequest = new GraphRequest( null, DEVICE_LOGIN_ENDPOINT, @@ -211,6 +216,13 @@ private void setCurrentRequestState(RequestState currentRequestState) { confirmationCode.setVisibility(View.VISIBLE); progressBar.setVisibility(View.GONE); + if (!isRetry) { + if (DeviceRequestsHelper.startAdvertisementService(currentRequestState.getUserCode())) { + final AppEventsLogger logger = AppEventsLogger.newLogger(getContext()); + logger.logSdkEvent(AnalyticsEvents.EVENT_SMART_LOGIN_SERVICE, null, null); + } + } + // If we polled within the last interval schedule a poll else start a poll. if (currentRequestState.withinLastRefreshWindow()) { schedulePoll(); @@ -219,6 +231,68 @@ private void setCurrentRequestState(RequestState currentRequestState) { } } + private void appendIconToTextView(final TextView textView, final String iconUriString) { + final int iconSize = 24; + ImageRequest request = new ImageRequest.Builder( + this.getContext(), + Uri.parse(iconUriString)) + .setCallback( new ImageRequest.Callback() { + @Override + public void onCompleted(ImageResponse response) { + if (response.getBitmap() != null) { + Bitmap bitmap = Bitmap.createScaledBitmap(response.getBitmap(), + iconSize, iconSize, false); + BitmapDrawable drawable = new BitmapDrawable(getResources(), + bitmap); + textView.setCompoundDrawablesWithIntrinsicBounds( + null, null, drawable, null); + } + + } + }).build(); + ImageDownloader.downloadAsync(request); + } + + private View initializeContentView(boolean isSmartLogin) { + View view; + LayoutInflater inflater = this.getActivity().getLayoutInflater(); + if (isSmartLogin) { + view = inflater.inflate(R.layout.com_facebook_smart_device_dialog_fragment, null); + + FetchedAppSettings settings = + FetchedAppSettingsManager.getAppSettingsWithoutQuery( + FacebookSdk.getApplicationId()); + if (settings.getSmartLoginBookmarkIconURL() != null) { + final TextView instructions2 = (TextView)view.findViewById( + R.id.com_facebook_smart_instructions_2); + this.appendIconToTextView(instructions2, settings.getSmartLoginBookmarkIconURL()); + } + if (settings.getSmartLoginMenuIconURL() != null) { + final TextView instructions1 = (TextView)view.findViewById( + R.id.com_facebook_smart_instructions_1); + this.appendIconToTextView(instructions1, settings.getSmartLoginMenuIconURL()); + } + } else { + view = inflater.inflate(R.layout.com_facebook_device_auth_dialog_fragment, null); + } + progressBar = (ProgressBar)view.findViewById(R.id.progress_bar); + confirmationCode = (TextView)view.findViewById(R.id.confirmation_code); + + Button cancelButton = (Button) view.findViewById(R.id.cancel_button); + cancelButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + onCancel(); + } + }); + + TextView instructions = (TextView)view.findViewById( + R.id.com_facebook_device_auth_instructions); + instructions.setText( + Html.fromHtml(getString(R.string.com_facebook_device_auth_instructions))); + return view; + } + private void poll() { currentRequestState.setLastPoll(new Date().getTime()); currentGraphRequestPoll = getPollRequest().executeAsync(); @@ -284,9 +358,37 @@ public void onCompleted(GraphResponse response) { }); } + private void presentConfirmation(final String userId, + final Utility.PermissionsPair permissions, + final String accessToken, + final String name) { + final String message = getResources().getString( + R.string.com_facebook_smart_login_confirmation_title); + final String continueFormat = getResources().getString( + R.string.com_facebook_smart_login_confirmation_continue_as); + final String cancel = getResources().getString( + R.string.com_facebook_smart_login_confirmation_cancel); + final String continueText = String.format(continueFormat, name); + AlertDialog.Builder builder = new AlertDialog.Builder(getContext()); + builder.setMessage(message) + .setCancelable(true) + .setNegativeButton(continueText, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface alertDialog, int which) { + completeLogin(userId, permissions, accessToken); + } + }) + .setPositiveButton(cancel, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface alertDialog, int which) { + View view = initializeContentView(false); + dialog.setContentView(view); + startLogin(mRequest); + } + }); + builder.create().show(); + } private void onSuccess(final String accessToken) { Bundle parameters = new Bundle(); - parameters.putString("fields", "id,permissions"); + parameters.putString("fields", "id,permissions,name"); AccessToken temporaryToken = new AccessToken( accessToken, FacebookSdk.getApplicationId(), @@ -316,35 +418,58 @@ public void onCompleted(GraphResponse response) { String userId; Utility.PermissionsPair permissions; + String name; try { JSONObject jsonObject = response.getJSONObject(); userId = jsonObject.getString("id"); permissions = Utility.handlePermissionResponse(jsonObject); + name = jsonObject.getString("name"); } catch (JSONException ex) { onError(new FacebookException(ex)); return; } + DeviceRequestsHelper.cleanUpAdvertisementService( + currentRequestState.getUserCode()); + + boolean requireConfirm = + FetchedAppSettingsManager. + getAppSettingsWithoutQuery(FacebookSdk.getApplicationId()). + getSmartLoginOptions().contains(SmartLoginOption.RequireConfirm); + if (requireConfirm && !isRetry) { + isRetry = true; + presentConfirmation(userId, permissions, accessToken, name); + return; + } - deviceAuthMethodHandler.onSuccess( - accessToken, - FacebookSdk.getApplicationId(), - userId, - permissions.getGrantedPermissions(), - permissions.getDeclinedPermissions(), - AccessTokenSource.DEVICE_AUTH, - null, - null); - dialog.dismiss(); + completeLogin(userId, permissions, accessToken); } }); request.executeAsync(); } + private void completeLogin(String userId, + Utility.PermissionsPair permissions, + String accessToken) { + deviceAuthMethodHandler.onSuccess( + accessToken, + FacebookSdk.getApplicationId(), + userId, + permissions.getGrantedPermissions(), + permissions.getDeclinedPermissions(), + AccessTokenSource.DEVICE_AUTH, + null, + null); + dialog.dismiss(); + } + private void onError(FacebookException ex) { if (!completed.compareAndSet(false, true)) { return; } + if (currentRequestState != null) { + DeviceRequestsHelper.cleanUpAdvertisementService(currentRequestState.getUserCode()); + } deviceAuthMethodHandler.onError(ex); dialog.dismiss(); } @@ -355,6 +480,8 @@ private void onCancel() { return; } + DeviceRequestsHelper.cleanUpAdvertisementService(currentRequestState.getUserCode()); + if (deviceAuthMethodHandler != null) { // We are detached and cannot send a cancel message back deviceAuthMethodHandler.onCancel(); diff --git a/facebook/src/main/java/com/facebook/login/DeviceAuthMethodHandler.java b/facebook/src/main/java/com/facebook/login/DeviceAuthMethodHandler.java index 9539525f32..e034b18e83 100644 --- a/facebook/src/main/java/com/facebook/login/DeviceAuthMethodHandler.java +++ b/facebook/src/main/java/com/facebook/login/DeviceAuthMethodHandler.java @@ -20,7 +20,6 @@ package com.facebook.login; -import android.app.Dialog; import android.os.Parcel; import android.os.Parcelable; diff --git a/facebook/src/main/java/com/facebook/login/FacebookLiteLoginMethodHandler.java b/facebook/src/main/java/com/facebook/login/FacebookLiteLoginMethodHandler.java index a394062782..4de6dfa9be 100644 --- a/facebook/src/main/java/com/facebook/login/FacebookLiteLoginMethodHandler.java +++ b/facebook/src/main/java/com/facebook/login/FacebookLiteLoginMethodHandler.java @@ -20,19 +20,12 @@ package com.facebook.login; -import android.app.Activity; -import android.content.ActivityNotFoundException; import android.content.Intent; -import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; -import com.facebook.AccessToken; -import com.facebook.AccessTokenSource; -import com.facebook.FacebookException; import com.facebook.internal.NativeProtocol; import com.facebook.internal.ServerProtocol; -import com.facebook.internal.Utility; class FacebookLiteLoginMethodHandler extends NativeAppLoginMethodHandler { diff --git a/facebook/src/main/java/com/facebook/login/KatanaProxyLoginMethodHandler.java b/facebook/src/main/java/com/facebook/login/KatanaProxyLoginMethodHandler.java index 0651b1c9a0..fee9eac8ff 100644 --- a/facebook/src/main/java/com/facebook/login/KatanaProxyLoginMethodHandler.java +++ b/facebook/src/main/java/com/facebook/login/KatanaProxyLoginMethodHandler.java @@ -20,19 +20,12 @@ package com.facebook.login; -import android.app.Activity; -import android.content.ActivityNotFoundException; import android.content.Intent; -import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; -import com.facebook.AccessToken; -import com.facebook.AccessTokenSource; -import com.facebook.FacebookException; import com.facebook.internal.NativeProtocol; import com.facebook.internal.ServerProtocol; -import com.facebook.internal.Utility; class KatanaProxyLoginMethodHandler extends NativeAppLoginMethodHandler { diff --git a/facebook/src/main/java/com/facebook/login/LoginBehavior.java b/facebook/src/main/java/com/facebook/login/LoginBehavior.java index a7b8f19571..45c3cac4ba 100644 --- a/facebook/src/main/java/com/facebook/login/LoginBehavior.java +++ b/facebook/src/main/java/com/facebook/login/LoginBehavior.java @@ -65,7 +65,7 @@ public enum LoginBehavior { private final boolean allowsCustomTabAuth; private final boolean allowsFacebookLiteAuth; - private LoginBehavior( + LoginBehavior( boolean allowsGetTokenAuth, boolean allowsKatanaAuth, boolean allowsWebViewAuth, diff --git a/facebook/src/main/java/com/facebook/login/LoginClient.java b/facebook/src/main/java/com/facebook/login/LoginClient.java index e0beee610e..486b26b9ee 100644 --- a/facebook/src/main/java/com/facebook/login/LoginClient.java +++ b/facebook/src/main/java/com/facebook/login/LoginClient.java @@ -24,8 +24,6 @@ import android.app.Activity; import android.content.Intent; import android.content.pm.PackageManager; -import android.net.Uri; -import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; import android.support.v4.app.Fragment; @@ -33,17 +31,13 @@ import android.text.TextUtils; import com.facebook.AccessToken; -import com.facebook.GraphRequest; -import com.facebook.GraphResponse; -import com.facebook.appevents.AppEventsConstants; import com.facebook.FacebookException; -import com.facebook.HttpMethod; import com.facebook.R; +import com.facebook.appevents.AppEventsConstants; import com.facebook.internal.CallbackManagerImpl; import com.facebook.internal.Utility; import com.facebook.internal.Validate; -import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; @@ -51,7 +45,6 @@ import java.util.Collection; import java.util.HashMap; import java.util.HashSet; -import java.util.List; import java.util.Map; import java.util.Set; @@ -341,7 +334,7 @@ void validateSameFbidAndFinish(Result pendingResult) { AccessToken newToken = pendingResult.token; try { - Result result = null; + Result result; if (previousToken != null && newToken != null && previousToken.getUserId().equals(newToken.getUserId())) { result = Result.createTokenResult(pendingRequest, pendingResult.token); @@ -524,7 +517,7 @@ private Request(Parcel parcel) { this.defaultAudience = enumValue != null ? DefaultAudience.valueOf(enumValue) : null; this.applicationId = parcel.readString(); this.authId = parcel.readString(); - this.isRerequest = parcel.readByte() != 0 ? true : false; + this.isRerequest = parcel.readByte() != 0; this.deviceRedirectUriString = parcel.readString(); } diff --git a/facebook/src/main/java/com/facebook/login/LoginFragment.java b/facebook/src/main/java/com/facebook/login/LoginFragment.java index e067567911..737e3ddc71 100644 --- a/facebook/src/main/java/com/facebook/login/LoginFragment.java +++ b/facebook/src/main/java/com/facebook/login/LoginFragment.java @@ -81,7 +81,7 @@ public void onCompleted(LoginClient.Result outcome) { if (activity.getIntent() != null) { Intent intent = activity.getIntent(); Bundle bundle = intent.getBundleExtra(REQUEST_KEY); - request = (LoginClient.Request)bundle.getParcelable(EXTRA_REQUEST); + request = bundle.getParcelable(EXTRA_REQUEST); } } diff --git a/facebook/src/main/java/com/facebook/login/LoginLogger.java b/facebook/src/main/java/com/facebook/login/LoginLogger.java index ad6cefbb28..d8ca97151a 100644 --- a/facebook/src/main/java/com/facebook/login/LoginLogger.java +++ b/facebook/src/main/java/com/facebook/login/LoginLogger.java @@ -143,7 +143,7 @@ public void logCompleteLogin(String loginRequestId, Map loggingE // Combine extras from the request and from the result. JSONObject jsonObject = null; - if (loggingExtras.isEmpty() == false) { + if (!loggingExtras.isEmpty()) { jsonObject = new JSONObject(loggingExtras); } if (resultExtras != null) { diff --git a/facebook/src/main/java/com/facebook/login/LoginManager.java b/facebook/src/main/java/com/facebook/login/LoginManager.java index ac02241677..a43df449ab 100644 --- a/facebook/src/main/java/com/facebook/login/LoginManager.java +++ b/facebook/src/main/java/com/facebook/login/LoginManager.java @@ -179,7 +179,7 @@ boolean onActivityResult(int resultCode, Intent data, FacebookCallback permissions = Collections.emptyList(); + private List permissions = Collections.emptyList(); private LoginAuthorizationType authorizationType = null; private LoginBehavior loginBehavior = LoginBehavior.NATIVE_WITH_FALLBACK; @@ -488,7 +496,7 @@ private void checkToolTipSettings() { FacebookSdk.getExecutor().execute(new Runnable() { @Override public void run() { - final FetchedAppSettings settings = Utility.queryAppSettings(appId, false); + final FetchedAppSettings settings = FetchedAppSettingsManager.queryAppSettings(appId, false); getActivity().runOnUiThread(new Runnable() { @Override public void run() { @@ -638,11 +646,10 @@ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { private int measureButtonWidth(final String text) { int textWidth = measureTextWidth(text); - int width = (getCompoundPaddingLeft() + + return (getCompoundPaddingLeft() + getCompoundDrawablePadding() + textWidth + getCompoundPaddingRight()); - return width; } private void setButtonText() { diff --git a/facebook/src/main/java/com/facebook/share/DeviceShareDialog.java b/facebook/src/main/java/com/facebook/share/DeviceShareDialog.java index c9c0171245..bbf8422e84 100644 --- a/facebook/src/main/java/com/facebook/share/DeviceShareDialog.java +++ b/facebook/src/main/java/com/facebook/share/DeviceShareDialog.java @@ -22,7 +22,6 @@ import android.app.Activity; import android.app.Fragment; import android.content.Intent; -import android.os.Bundle; import com.facebook.FacebookActivity; import com.facebook.FacebookCallback; @@ -121,7 +120,6 @@ protected void registerCallbackImpl( new CallbackManagerImpl.Callback() { @Override public boolean onActivityResult(int resultCode, Intent data) { - Bundle extras = data.getExtras(); if (data.hasExtra("error")) { FacebookRequestError error = data.getParcelableExtra("error"); callback.onError(error.getException()); diff --git a/facebook/src/main/java/com/facebook/share/ShareApi.java b/facebook/src/main/java/com/facebook/share/ShareApi.java index 2917e345e2..7940f63f22 100644 --- a/facebook/src/main/java/com/facebook/share/ShareApi.java +++ b/facebook/src/main/java/com/facebook/share/ShareApi.java @@ -350,7 +350,7 @@ public void onCompleted(GraphResponse response) { }; try { for (SharePhoto photo : photoContent.getPhotos()) { - Bundle params = null; + Bundle params; try { params = getSharePhotoCommonParameters(photo, photoContent); } catch (JSONException e) { diff --git a/facebook/src/main/java/com/facebook/share/internal/DeviceShareDialogFragment.java b/facebook/src/main/java/com/facebook/share/internal/DeviceShareDialogFragment.java index 82bc20cef2..9a974f2fc7 100644 --- a/facebook/src/main/java/com/facebook/share/internal/DeviceShareDialogFragment.java +++ b/facebook/src/main/java/com/facebook/share/internal/DeviceShareDialogFragment.java @@ -43,6 +43,7 @@ import com.facebook.GraphResponse; import com.facebook.HttpMethod; import com.facebook.R; +import com.facebook.devicerequests.internal.DeviceRequestsHelper; import com.facebook.internal.Validate; import com.facebook.share.model.ShareContent; import com.facebook.share.model.ShareLinkContent; @@ -135,6 +136,8 @@ public void onSaveInstanceState(Bundle outState) { } private void finishActivity(int resultCode, Intent data) { + DeviceRequestsHelper.cleanUpAdvertisementService(currentRequestState.getUserCode()); + if (isAdded()) { Activity activity = getActivity(); activity.setResult(resultCode, data); @@ -171,8 +174,12 @@ private void startShare() { this.finishActivityWithError( new FacebookRequestError(0, "", "Failed to get share content")); } + String accessToken = Validate.hasAppID()+ "|" + Validate.hasClientToken(); parameters.putString(GraphRequest.ACCESS_TOKEN_PARAM, accessToken); + parameters.putString(DeviceRequestsHelper.DEVICE_INFO_PARAM, + DeviceRequestsHelper.getDeviceInfo()); + GraphRequest graphRequest = new GraphRequest( null, DEVICE_SHARE_ENDPOINT, diff --git a/facebook/src/main/java/com/facebook/share/internal/LegacyNativeDialogParameters.java b/facebook/src/main/java/com/facebook/share/internal/LegacyNativeDialogParameters.java index a00f9e5fc4..7b187ac5ba 100644 --- a/facebook/src/main/java/com/facebook/share/internal/LegacyNativeDialogParameters.java +++ b/facebook/src/main/java/com/facebook/share/internal/LegacyNativeDialogParameters.java @@ -20,7 +20,6 @@ package com.facebook.share.internal; -import android.content.Context; import android.os.Bundle; import com.facebook.FacebookException; @@ -28,7 +27,6 @@ import com.facebook.internal.Validate; import com.facebook.share.model.ShareContent; import com.facebook.share.model.ShareLinkContent; -import com.facebook.share.model.ShareOpenGraphAction; import com.facebook.share.model.ShareOpenGraphContent; import com.facebook.share.model.SharePhotoContent; import com.facebook.share.model.ShareVideoContent; diff --git a/facebook/src/main/java/com/facebook/share/internal/OpenGraphJSONUtility.java b/facebook/src/main/java/com/facebook/share/internal/OpenGraphJSONUtility.java index dfa01941df..787600ca2d 100644 --- a/facebook/src/main/java/com/facebook/share/internal/OpenGraphJSONUtility.java +++ b/facebook/src/main/java/com/facebook/share/internal/OpenGraphJSONUtility.java @@ -20,9 +20,8 @@ package com.facebook.share.internal; -import android.os.Bundle; import android.support.annotation.Nullable; -import com.facebook.internal.Validate; + import com.facebook.share.model.ShareOpenGraphAction; import com.facebook.share.model.ShareOpenGraphObject; import com.facebook.share.model.SharePhoto; diff --git a/facebook/src/main/java/com/facebook/share/internal/ShareContentValidation.java b/facebook/src/main/java/com/facebook/share/internal/ShareContentValidation.java index bd5643b1c4..728534c7e9 100644 --- a/facebook/src/main/java/com/facebook/share/internal/ShareContentValidation.java +++ b/facebook/src/main/java/com/facebook/share/internal/ShareContentValidation.java @@ -260,7 +260,7 @@ private static void validateOpenGraphAction( throw new FacebookException("ShareOpenGraphAction must have a non-empty actionType"); } - validator.validate((ShareOpenGraphValueContainer) openGraphAction, false); + validator.validate(openGraphAction, false); } private static void validateOpenGraphObject( @@ -270,7 +270,7 @@ private static void validateOpenGraphObject( throw new FacebookException("Cannot share a null ShareOpenGraphObject"); } - validator.validate((ShareOpenGraphValueContainer) openGraphObject, true); + validator.validate(openGraphObject, true); } private static void validateOpenGraphValueContainer( diff --git a/facebook/src/main/java/com/facebook/share/internal/ShareFeedContent.java b/facebook/src/main/java/com/facebook/share/internal/ShareFeedContent.java index 295ca40473..e3f3cdfdd6 100644 --- a/facebook/src/main/java/com/facebook/share/internal/ShareFeedContent.java +++ b/facebook/src/main/java/com/facebook/share/internal/ShareFeedContent.java @@ -25,9 +25,6 @@ import com.facebook.share.model.ShareContent; -import java.util.HashMap; -import java.util.Map; - // This class is used specifically for backwards support in unity for various feed parameters // Currently this content is only supported if you set the mode to Feed when sharing. public class ShareFeedContent diff --git a/facebook/src/main/java/com/facebook/share/internal/ShareInternalUtility.java b/facebook/src/main/java/com/facebook/share/internal/ShareInternalUtility.java index 4e8305eb27..c5c8d365f7 100644 --- a/facebook/src/main/java/com/facebook/share/internal/ShareInternalUtility.java +++ b/facebook/src/main/java/com/facebook/share/internal/ShareInternalUtility.java @@ -401,7 +401,7 @@ public JSONObject toJSONObject(SharePhoto photo) { for (String peopleId : content.getPeopleIds()) { peopleIdSet.add(peopleId); } - actionJSON.put("tags", new ArrayList<>(peopleIdSet)); + actionJSON.put("tags", new JSONArray(peopleIdSet)); } return actionJSON; @@ -460,7 +460,7 @@ public static JSONObject removeNamespacesFromOGJsonObject( JSONArray names = jsonObject.names(); for (int i = 0; i < names.length(); ++i) { String key = names.getString(i); - Object value = null; + Object value; value = jsonObject.get(key); if (value instanceof JSONObject) { value = removeNamespacesFromOGJsonObject((JSONObject) value, true); diff --git a/facebook/src/main/java/com/facebook/share/internal/VideoUploader.java b/facebook/src/main/java/com/facebook/share/internal/VideoUploader.java index 52a012d9df..48c370a227 100644 --- a/facebook/src/main/java/com/facebook/share/internal/VideoUploader.java +++ b/facebook/src/main/java/com/facebook/share/internal/VideoUploader.java @@ -230,7 +230,7 @@ private static byte[] getChunk( int bufferSize = Math.min(8192, chunkSize); byte[] buffer = new byte[bufferSize]; - int len = 0; + int len; while ((len = uploadContext.videoStream.read(buffer)) != -1) { byteBufferStream.write(buffer, 0, len); @@ -333,7 +333,7 @@ private UploadContext( private void initialize() throws FileNotFoundException { - ParcelFileDescriptor fileDescriptor = null; + ParcelFileDescriptor fileDescriptor; try { if (Utility.isFileUri(videoUri)) { fileDescriptor = ParcelFileDescriptor.open( diff --git a/facebook/src/main/java/com/facebook/share/internal/WebDialogParameters.java b/facebook/src/main/java/com/facebook/share/internal/WebDialogParameters.java index fd0cef4642..ca2b5f5d89 100644 --- a/facebook/src/main/java/com/facebook/share/internal/WebDialogParameters.java +++ b/facebook/src/main/java/com/facebook/share/internal/WebDialogParameters.java @@ -33,12 +33,9 @@ import com.facebook.share.model.SharePhoto; import com.facebook.share.model.SharePhotoContent; -import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; -import java.util.ArrayList; -import java.util.List; import java.util.Locale; /** diff --git a/facebook/src/main/java/com/facebook/share/model/GameRequestContent.java b/facebook/src/main/java/com/facebook/share/model/GameRequestContent.java index d9d185471c..cc4fa0c2bd 100644 --- a/facebook/src/main/java/com/facebook/share/model/GameRequestContent.java +++ b/facebook/src/main/java/com/facebook/share/model/GameRequestContent.java @@ -23,7 +23,6 @@ import android.os.Parcel; import android.text.TextUtils; -import java.util.ArrayList; import java.util.Arrays; import java.util.List; diff --git a/facebook/src/main/java/com/facebook/share/model/ShareMedia.java b/facebook/src/main/java/com/facebook/share/model/ShareMedia.java index f1be45be7f..96efc6b299 100644 --- a/facebook/src/main/java/com/facebook/share/model/ShareMedia.java +++ b/facebook/src/main/java/com/facebook/share/model/ShareMedia.java @@ -22,11 +22,9 @@ import android.os.Bundle; import android.os.Parcel; -import android.os.ParcelFormatException; import android.os.Parcelable; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; /** diff --git a/facebook/src/main/java/com/facebook/share/model/ShareModelBuilder.java b/facebook/src/main/java/com/facebook/share/model/ShareModelBuilder.java index 4e8fe2968f..3500dde49b 100644 --- a/facebook/src/main/java/com/facebook/share/model/ShareModelBuilder.java +++ b/facebook/src/main/java/com/facebook/share/model/ShareModelBuilder.java @@ -20,8 +20,6 @@ package com.facebook.share.model; -import android.os.Parcel; - import com.facebook.share.ShareBuilder; /** diff --git a/facebook/src/main/java/com/facebook/share/model/SharePhotoContent.java b/facebook/src/main/java/com/facebook/share/model/SharePhotoContent.java index fccbc67dec..8b740bf7bb 100644 --- a/facebook/src/main/java/com/facebook/share/model/SharePhotoContent.java +++ b/facebook/src/main/java/com/facebook/share/model/SharePhotoContent.java @@ -23,8 +23,6 @@ import android.os.Parcel; import android.support.annotation.Nullable; -import com.facebook.share.internal.ShareConstants; - import java.util.ArrayList; import java.util.Collections; import java.util.List; diff --git a/facebook/src/main/java/com/facebook/share/widget/AppInviteDialog.java b/facebook/src/main/java/com/facebook/share/widget/AppInviteDialog.java index d65bdcbd05..3266093e07 100644 --- a/facebook/src/main/java/com/facebook/share/widget/AppInviteDialog.java +++ b/facebook/src/main/java/com/facebook/share/widget/AppInviteDialog.java @@ -32,7 +32,6 @@ import com.facebook.share.internal.*; import com.facebook.share.model.AppInviteContent; -import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; diff --git a/facebook/src/main/java/com/facebook/share/widget/MessageDialog.java b/facebook/src/main/java/com/facebook/share/widget/MessageDialog.java index 01fb169439..03abaabd0b 100644 --- a/facebook/src/main/java/com/facebook/share/widget/MessageDialog.java +++ b/facebook/src/main/java/com/facebook/share/widget/MessageDialog.java @@ -212,7 +212,6 @@ public AppCall createAppCall(final ShareContent content) { final AppCall appCall = createBaseAppCall(); final boolean shouldFailOnDataError = getShouldFailOnDataError(); - final Activity activity = getActivityContext(); DialogPresenter.setupAppCallForNativeDialog( appCall, diff --git a/facebook/src/main/java/com/facebook/share/widget/SendButton.java b/facebook/src/main/java/com/facebook/share/widget/SendButton.java index 068d9ebc63..a85330561c 100644 --- a/facebook/src/main/java/com/facebook/share/widget/SendButton.java +++ b/facebook/src/main/java/com/facebook/share/widget/SendButton.java @@ -22,7 +22,6 @@ import android.content.Context; import android.util.AttributeSet; -import android.view.View; import com.facebook.R; import com.facebook.internal.AnalyticsEvents; diff --git a/facebook/src/main/java/com/facebook/share/widget/ShareButton.java b/facebook/src/main/java/com/facebook/share/widget/ShareButton.java index 2c5835150e..810218227b 100644 --- a/facebook/src/main/java/com/facebook/share/widget/ShareButton.java +++ b/facebook/src/main/java/com/facebook/share/widget/ShareButton.java @@ -22,7 +22,6 @@ import android.content.Context; import android.util.AttributeSet; -import android.view.View; import com.facebook.R; import com.facebook.internal.AnalyticsEvents; diff --git a/facebook/src/main/java/com/facebook/share/widget/ShareDialog.java b/facebook/src/main/java/com/facebook/share/widget/ShareDialog.java index 4613c6a25a..0ad84e3991 100644 --- a/facebook/src/main/java/com/facebook/share/widget/ShareDialog.java +++ b/facebook/src/main/java/com/facebook/share/widget/ShareDialog.java @@ -27,6 +27,7 @@ import android.os.Bundle; import android.support.v4.app.Fragment; +import com.facebook.AccessToken; import com.facebook.FacebookCallback; import com.facebook.appevents.AppEventsLogger; import com.facebook.internal.AnalyticsEvents; @@ -168,9 +169,14 @@ private static boolean canShowWebTypeCheck(Class content // The instance method version of this check is more accurate and should be used on // ShareDialog instances. + // SharePhotoContent currently requires the user staging endpoint, so we need a user access + // token, so we need to see if we have one + final AccessToken accessToken = AccessToken.getCurrentAccessToken(); + final boolean haveUserAccessToken = accessToken != null && !accessToken.isExpired(); + return ShareLinkContent.class.isAssignableFrom(contentType) || ShareOpenGraphContent.class.isAssignableFrom(contentType) - || SharePhotoContent.class.isAssignableFrom(contentType); + || (SharePhotoContent.class.isAssignableFrom(contentType) && haveUserAccessToken); } /** diff --git a/facebook/src/main/res/layout/com_facebook_device_auth_dialog_fragment.xml b/facebook/src/main/res/layout/com_facebook_device_auth_dialog_fragment.xml index 5b7001999e..3acc823e00 100644 --- a/facebook/src/main/res/layout/com_facebook_device_auth_dialog_fragment.xml +++ b/facebook/src/main/res/layout/com_facebook_device_auth_dialog_fragment.xml @@ -27,13 +27,13 @@ app:cardElevation="10dp"> @@ -93,7 +93,7 @@ diff --git a/facebook/src/main/res/layout/com_facebook_smart_device_dialog_fragment.xml b/facebook/src/main/res/layout/com_facebook_smart_device_dialog_fragment.xml new file mode 100644 index 0000000000..e86adda679 --- /dev/null +++ b/facebook/src/main/res/layout/com_facebook_smart_device_dialog_fragment.xml @@ -0,0 +1,155 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +