com.facebook.TestUserManager.java Source code

Java tutorial

Introduction

Here is the source code for com.facebook.TestUserManager.java

Source

/**
 * Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
 *
 * You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
 * copy, modify, and distribute this software in source code or binary form for use
 * in connection with the web services and APIs provided by Facebook.
 *
 * As with any software that integrates with the Facebook platform, your use of
 * this software is subject to the Facebook Developer Principles and Policies
 * [http://developers.facebook.com/policy/]. This copyright notice shall be
 * included in all copies or substantial portions of the software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
 * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

package com.facebook;

import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;

import com.facebook.internal.Utility;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * This class manages Facebook test users.
 */
public class TestUserManager {

    private static final String LOG_TAG = "TestUserManager";

    private enum Mode {
        PRIVATE, SHARED,
    }

    private String testApplicationSecret;
    private String testApplicationId;
    private Map<String, JSONObject> appTestAccounts;

    /**
     * Constructor.
     *
     * @param testApplicationSecret The application secret.
     * @param testApplicationId     The application id.
     */
    public TestUserManager(String testApplicationSecret, String testApplicationId) {
        if (Utility.isNullOrEmpty(testApplicationId) || Utility.isNullOrEmpty(testApplicationSecret)) {
            throw new FacebookException("Must provide app ID and secret");
        }

        this.testApplicationSecret = testApplicationSecret;
        this.testApplicationId = testApplicationId;
    }

    /**
     * Gets the access token of the private test user for the application with the requested
     * permissions.
     *
     * @param permissions The requested permissions.
     * @return The access token of the private test user for the application.
     */
    public AccessToken getAccessTokenForPrivateUser(List<String> permissions) {
        return getAccessTokenForUser(permissions, Mode.PRIVATE, null);
    }

    /**
     * Gets the access token of the shared test user for the application with the requested
     * permissions.
     *
     * @param permissions The requested permissions.
     * @return The access token of the shared test user for the application.
     */
    public AccessToken getAccessTokenForSharedUser(List<String> permissions) {
        return getAccessTokenForSharedUser(permissions, null);
    }

    /**
     * Gets the access token of the shared test user with the tag for the application with the
     * requested permissions.
     *
     * @param permissions   The requested permissions.
     * @param uniqueUserTag The user tag.
     * @return The requested shared user.
     */
    public AccessToken getAccessTokenForSharedUser(List<String> permissions, String uniqueUserTag) {
        return getAccessTokenForUser(permissions, Mode.SHARED, uniqueUserTag);
    }

    /**
     * Getter for the test application id.
     *
     * @return The test application id.
     */
    public synchronized String getTestApplicationId() {
        return testApplicationId;
    }

    /**
     * Getter for the test application secret.
     *
     * @return The test application secret.
     */
    public synchronized String getTestApplicationSecret() {
        return testApplicationSecret;
    }

    private AccessToken getAccessTokenForUser(List<String> permissions, Mode mode, String uniqueUserTag) {

        retrieveTestAccountsForAppIfNeeded();

        if (Utility.isNullOrEmpty(permissions)) {
            permissions = Arrays.asList("email", "publish_actions");
        }

        JSONObject testAccount = null;
        if (mode == Mode.PRIVATE) {
            testAccount = createTestAccount(permissions, mode, uniqueUserTag);
        } else {
            testAccount = findOrCreateSharedTestAccount(permissions, mode, uniqueUserTag);
        }

        return new AccessToken(testAccount.optString("access_token"), testApplicationId,
                testAccount.optString("id"), permissions, null, AccessTokenSource.TEST_USER, null, null);
    }

    private synchronized void retrieveTestAccountsForAppIfNeeded() {
        if (appTestAccounts != null) {
            return;
        }

        appTestAccounts = new HashMap<String, JSONObject>();

        // The data we need is split across two different graph API queries. We construct two
        // queries, submit them together (the second one depends on the first one), then
        // cross-reference the results.

        GraphRequest.setDefaultBatchApplicationId(testApplicationId);

        Bundle parameters = new Bundle();
        parameters.putString("access_token", getAppAccessToken());

        GraphRequest requestTestUsers = new GraphRequest(null, "app/accounts/test-users", parameters, null);
        requestTestUsers.setBatchEntryName("testUsers");
        requestTestUsers.setBatchEntryOmitResultOnSuccess(false);

        Bundle testUserNamesParam = new Bundle();
        testUserNamesParam.putString("access_token", getAppAccessToken());
        testUserNamesParam.putString("ids", "{result=testUsers:$.data.*.id}");
        testUserNamesParam.putString("fields", "name");

        GraphRequest requestTestUserNames = new GraphRequest(null, "", testUserNamesParam, null);
        requestTestUserNames.setBatchEntryDependsOn("testUsers");

        List<GraphResponse> responses = GraphRequest.executeBatchAndWait(requestTestUsers, requestTestUserNames);
        if (responses == null || responses.size() != 2) {
            throw new FacebookException("Unexpected number of results from TestUsers batch query");
        }

        JSONObject testAccountsResponse = responses.get(0).getJSONObject();
        JSONArray testAccounts = testAccountsResponse.optJSONArray("data");

        // Response should contain a map of test accounts: { id's => { user } }
        JSONObject userAccountsMap = responses.get(1).getJSONObject();

        populateTestAccounts(testAccounts, userAccountsMap);
    }

    private synchronized void populateTestAccounts(JSONArray testAccounts, JSONObject userAccountsMap) {

        for (int i = 0; i < testAccounts.length(); ++i) {
            JSONObject testAccount = testAccounts.optJSONObject(i);
            JSONObject testUser = userAccountsMap.optJSONObject(testAccount.optString("id"));
            try {
                testAccount.put("name", testUser.optString("name"));
            } catch (JSONException e) {
                Log.e(LOG_TAG, "Could not set name", e);
            }
            storeTestAccount(testAccount);
        }
    }

    private synchronized void storeTestAccount(JSONObject testAccount) {
        appTestAccounts.put(testAccount.optString("id"), testAccount);
    }

    private synchronized JSONObject findTestAccountMatchingIdentifier(String identifier) {
        for (JSONObject testAccount : appTestAccounts.values()) {
            if (testAccount.optString("name").contains(identifier)) {
                return testAccount;
            }
        }
        return null;
    }

    final String getAppAccessToken() {
        return testApplicationId + "|" + testApplicationSecret;
    }

    private JSONObject findOrCreateSharedTestAccount(List<String> permissions, Mode mode, String uniqueUserTag) {

        JSONObject testAccount = findTestAccountMatchingIdentifier(
                getSharedTestAccountIdentifier(permissions, uniqueUserTag));
        if (testAccount != null) {
            return testAccount;
        } else {
            return createTestAccount(permissions, mode, uniqueUserTag);
        }
    }

    private String getSharedTestAccountIdentifier(List<String> permissions, String uniqueUserTag) {

        // We use long even though hashes are ints to avoid sign issues.
        long permissionsHash = getPermissionsString(permissions).hashCode() & 0xffffffffL;
        long userTagHash = (uniqueUserTag != null) ? uniqueUserTag.hashCode() & 0xffffffffL : 0;

        long combinedHash = permissionsHash ^ userTagHash;
        return validNameStringFromInteger(combinedHash);
    }

    private String validNameStringFromInteger(long i) {
        String s = Long.toString(i);
        StringBuilder result = new StringBuilder("Perm");

        // We know each character is a digit. Convert it into a letter 'a'-'j'. Avoid repeated
        //  characters that might make Facebook reject the name by converting every other repeated
        //  character into one 10 higher ('k'-'t').
        char lastChar = 0;
        for (char c : s.toCharArray()) {
            if (c == lastChar) {
                c += 10;
            }
            result.append((char) (c + 'a' - '0'));
            lastChar = c;
        }

        return result.toString();
    }

    private JSONObject createTestAccount(List<String> permissions, Mode mode, String uniqueUserTag) {
        Bundle parameters = new Bundle();
        parameters.putString("installed", "true");
        parameters.putString("permissions", getPermissionsString(permissions));
        parameters.putString("access_token", getAppAccessToken());

        // If we're in shared mode, we want to rename this user to encode its permissions, so we can
        // find it later. If we're in private mode, don't bother renaming it since we're just going
        // to delete it at the end.
        if (mode == Mode.SHARED) {
            parameters.putString("name", String.format("Shared %s Testuser",
                    getSharedTestAccountIdentifier(permissions, uniqueUserTag)));
        }

        String graphPath = String.format("%s/accounts/test-users", testApplicationId);
        GraphRequest createUserRequest = new GraphRequest(null, graphPath, parameters, HttpMethod.POST);
        GraphResponse response = createUserRequest.executeAndWait();

        FacebookRequestError error = response.getError();
        JSONObject testAccount = response.getJSONObject();
        if (error != null) {
            return null;
        } else {
            assert testAccount != null;

            // If we are in shared mode, store this new account in the dictionary so we can re-use
            // it later.
            if (mode == Mode.SHARED) {
                // Remember the new name we gave it, since we didn't get it back in the results of
                // the create request.
                try {
                    testAccount.put("name", parameters.getString("name"));
                } catch (JSONException e) {
                    Log.e(LOG_TAG, "Could not set name", e);
                }
                storeTestAccount(testAccount);
            }

            return testAccount;
        }
    }

    private String getPermissionsString(List<String> permissions) {
        return TextUtils.join(",", permissions);
    }
}