Android Open Source - kickflip-android-sdk Kickflip Api Client






From Project

Back to project page kickflip-android-sdk.

License

The source code is released under:

Apache License

If you think the Android project kickflip-android-sdk listed in this page is inappropriate, such as containing malicious code/tools or violating the copyright, please email info at java2s dot com, thanks.

Java Source Code

package io.kickflip.sdk.api;
//from  w w  w  .ja va  2 s. c o  m
import android.content.Context;
import android.content.SharedPreferences;
import android.location.Location;
import android.os.Handler;
import android.util.Log;

import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.util.json.Jackson;
import com.google.api.client.http.GenericUrl;
import com.google.api.client.http.HttpContent;
import com.google.api.client.http.HttpRequest;
import com.google.api.client.http.HttpRequestFactory;
import com.google.api.client.http.HttpResponse;
import com.google.api.client.http.HttpResponseException;
import com.google.api.client.http.UrlEncodedContent;
import com.google.api.client.json.JsonFactory;
import com.google.api.client.json.JsonObjectParser;
import com.google.api.client.json.jackson2.JacksonFactory;
import com.google.api.client.util.GenericData;

import java.io.EOFException;
import java.io.IOException;
import java.math.BigInteger;
import java.security.SecureRandom;
import java.util.HashMap;
import java.util.Map;

import io.kickflip.sdk.R;
import io.kickflip.sdk.api.json.HlsStream;
import io.kickflip.sdk.api.json.Response;
import io.kickflip.sdk.api.json.Stream;
import io.kickflip.sdk.api.json.StreamList;
import io.kickflip.sdk.api.json.User;
import io.kickflip.sdk.exception.KickflipException;

import static com.google.common.base.Preconditions.checkNotNull;

/**
 * Kickflip API Client
 * <p/>
 * After construction, requests can be immediately performed.
 * The client will handle acquiring and refreshing OAuth
 * Access tokens as needed.
 * <p/>
 * The client is intended to manage a unique Kickflip user per Android device installation.
 */
// TODO: Standardize Kickflip server error responses to have detail message
public class KickflipApiClient extends OAuthClient {
    private static final String TAG                 = "KickflipApiClient";
    private static final boolean VERBOSE            = false;
    private static final boolean DEV_ENDPOINT       = false;
    private static final String NEW_USER            = "/user/new";
    private static final String GET_USER_PUBLIC     = "/user/info";
    private static final String GET_USER_PRIVATE    = "/user/uuid";
    private static final String EDIT_USER           = "/user/change";
    private static final String START_STREAM        = "/stream/start";
    private static final String STOP_STREAM         = "/stream/stop";
    private static final String SET_META            = "/stream/change";
    private static final String GET_META            = "/stream/info";
    private static final String FLAG_STREAM         = "/stream/flag";
    private static final String SEARCH_KEYWORD      = "/search";
    private static final String SEARCH_USER         = "/search/user";
    private static final String SEARCH_GEO          = "/search/location";
    private static final String API_VERSION         = "/1.1";
    private static final int MAX_EOF_RETRIES        = 1;
    private static int UNKNOWN_ERROR_CODE           = R.integer.generic_error;    // Error code used when none provided from server
    private static String BASE_URL;
    private JsonObjectParser mJsonObjectParser;             // Re-used across requests
    private JsonFactory mJsonFactory;                       // Re-used across requests

    private Handler mCallbackHandler;                       // Ensure callbacks are posted to consistent thread

    /**
     * Construct a KickflipApiClient. All callbacks from this client will occur
     * on the current calling thread.
     *
     * @param appContext Your Application Context
     * @param key        Your Kickflip Account Key
     * @param secret     Your Kickflip Account Secret
     */
    public KickflipApiClient(Context appContext, String key, String secret) {
        this(appContext, key, secret, null);
    }

    /**
     * Construct a KickflipApiClient. All callbacks from this client will occur
     * on the current calling thread.
     *
     * @param appContext Your Application Context
     * @param key        Your Kickflip Account Key
     * @param secret     Your Kickflip Account Secret
     * @param cb         A callback to be notified when the provided Kickflip credentials are verified
     */
    public KickflipApiClient(Context appContext, String key, String secret, KickflipCallback cb) {
        super(appContext, new OAuthConfig()
                .setCredentialStoreName("KF")
                .setClientId(key)
                .setClientSecret(secret)
                .setAccessTokenRequestUrl(BASE_URL + "/o/token/")
                .setAccessTokenAuthorizeUrl(BASE_URL + "/o/authorize/"));
        mCallbackHandler = new Handler();
        initialize(cb);
    }

    private void initialize(KickflipCallback cb) {
        if (getActiveUser() == null) {
            createNewUser(cb);
        } else {
            postResponseToCallback(cb, getActiveUser());
            if (VERBOSE)
                Log.i(TAG, "Credentials stored " + getAWSCredentials());
        }
    }

    /**
     * Create a new Kickflip User.
     * The User created as a result of this request is cached and managed by this KickflipApiClient
     * throughout the life of the host Android application installation.
     * <p/>
     * The other methods of this client will be performed on behalf of the user created by this request,
     * unless noted otherwise.
     *
     * @param username    The desired username for this Kickflip User. Will be altered if not unique for this Kickflip app.
     * @param password    The password for this Kickflip user.
     * @param email       The email address for this Kickflip user.
     * @param displayName The display name for this Kickflip user.
     * @param extraInfo   Map data to be associated with this Kickflip User.
     * @param cb          This callback will receive a User in {@link io.kickflip.sdk.api.KickflipCallback#onSuccess(io.kickflip.sdk.api.json.Response)}
     *                    or an Exception {@link io.kickflip.sdk.api.KickflipCallback#onError(io.kickflip.sdk.exception.KickflipException)}.
     */
    public void createNewUser(String username, String password, String email, String displayName, Map extraInfo, final KickflipCallback cb) {
        GenericData data = new GenericData();
        if (username != null) {
            data.put("username", username);
        }

        final String finalPassword;
        if (password != null) {
            finalPassword = password;
        } else {
            finalPassword = generateRandomPassword();
        }
        data.put("password", finalPassword);

        if (displayName != null) {
            data.put("display_name", displayName);
        }
        if (email != null) {
            data.put("email", email);
        }
        if (extraInfo != null) {
            data.put("extra_info", Jackson.toJsonString(extraInfo));
        }

        post(NEW_USER, new UrlEncodedContent(data), User.class, new KickflipCallback() {
            @Override
            public void onSuccess(final Response response) {
                if (VERBOSE)
                    Log.i(TAG, "createNewUser response: " + response);
                storeNewUserResponse((User) response, finalPassword);
                postResponseToCallback(cb, response);
            }

            @Override
            public void onError(final KickflipException error) {
                Log.w(TAG, "createNewUser Error: " + error);
                postExceptionToCallback(cb, error);
            }
        });
    }

    /**
     * Create a new Kickflip User.
     * The User created as a result of this request is active for this KickflipApiClient instance
     * throughout the life of the host Android application installation, until a subsequent call to this method
     * or {@link #loginUser(String, String, KickflipCallback)} }
     * <p/>
     * The other methods of this client will be performed on behalf of the user created by this request,
     * unless noted otherwise.
     *
     * @param cb This callback will receive a User in {@link io.kickflip.sdk.api.KickflipCallback#onSuccess(io.kickflip.sdk.api.json.Response)}
     *           or an Exception {@link io.kickflip.sdk.api.KickflipCallback#onError(io.kickflip.sdk.exception.KickflipException)}.
     */
    public void createNewUser(final KickflipCallback cb) {
        final String password = generateRandomPassword();
        post(NEW_USER, User.class, new KickflipCallback() {
            @Override
            public void onSuccess(final Response response) {
                if (VERBOSE)
                    Log.i(TAG, "createNewUser response: " + response);
                storeNewUserResponse((User) response, password);
                postResponseToCallback(cb, response);
            }

            @Override
            public void onError(final KickflipException error) {
                Log.w(TAG, "createNewUser Error: " + error);
                postExceptionToCallback(cb, error);
            }
        });
    }

    /**
     * Login an exiting Kickflip User and make it active.
     *
     * @param username The Kickflip user's username
     * @param password The Kickflip user's password
     * @param cb       This callback will receive a User in {@link io.kickflip.sdk.api.KickflipCallback#onSuccess(io.kickflip.sdk.api.json.Response)}
     *                 or an Exception {@link io.kickflip.sdk.api.KickflipCallback#onError(io.kickflip.sdk.exception.KickflipException)}.
     */
    public void loginUser(String username, final String password, final KickflipCallback cb) {
        GenericData data = new GenericData();
        data.put("username", username);
        data.put("password", password);

        post(GET_USER_PRIVATE, new UrlEncodedContent(data), User.class, new KickflipCallback() {
            @Override
            public void onSuccess(final Response response) {
                if (VERBOSE)
                    Log.i(TAG, "loginUser response: " + response);
                storeNewUserResponse((User) response, password);
                postResponseToCallback(cb, response);
            }

            @Override
            public void onError(final KickflipException error) {
                Log.w(TAG, "loginUser Error: " + error);
                postExceptionToCallback(cb, error);
            }
        });
    }

    /**
     * Set the current active user's meta info. Pass a null argument to leave it as-is.
     *
     * @param newPassword the user's new password
     * @param email       the user's new email address
     * @param displayName The desired display name
     * @param extraInfo   Arbitrary String data to associate with this user.
     * @param cb          This callback will receive a User in {@link io.kickflip.sdk.api.KickflipCallback#onSuccess(io.kickflip.sdk.api.json.Response)}
     *                    or an Exception {@link io.kickflip.sdk.api.KickflipCallback#onError(io.kickflip.sdk.exception.KickflipException)}.
     */
    public void setUserInfo(final String newPassword, String email, String displayName, Map extraInfo, final KickflipCallback cb) {
        if (!assertActiveUserAvailable(cb)) return;
        GenericData data = new GenericData();
        final String finalPassword;
        if (newPassword != null){
            data.put("new_password", newPassword);
            finalPassword = newPassword;
        } else {
            finalPassword = getPasswordForActiveUser();
        }
        if (email != null) data.put("email", email);
        if (displayName != null) data.put("display_name", displayName);
        if (extraInfo != null) data.put("extra_info", Jackson.toJsonString(extraInfo));

        post(EDIT_USER, new UrlEncodedContent(data), User.class, new KickflipCallback() {
            @Override
            public void onSuccess(final Response response) {
                if (VERBOSE)
                    Log.i(TAG, "setUserInfo response: " + response);
                storeNewUserResponse((User) response, finalPassword);
                postResponseToCallback(cb, response);
            }

            @Override
            public void onError(final KickflipException error) {
                Log.w(TAG, "setUserInfo Error: " + error);
                postExceptionToCallback(cb, error);
            }
        });
    }

    /**
     * Get public user info
     *
     * @param username The Kickflip user's username
     * @param cb       This callback will receive a User in {@link io.kickflip.sdk.api.KickflipCallback#onSuccess(io.kickflip.sdk.api.json.Response)}
     *                 or an Exception {@link io.kickflip.sdk.api.KickflipCallback#onError(io.kickflip.sdk.exception.KickflipException)}.
     */
    public void getUserInfo(String username, final KickflipCallback cb) {
        if (!assertActiveUserAvailable(cb)) return;
        GenericData data = new GenericData();
        data.put("username", username);

        post(GET_USER_PUBLIC, new UrlEncodedContent(data), User.class, new KickflipCallback() {
            @Override
            public void onSuccess(final Response response) {
                if (VERBOSE)
                    Log.i(TAG, "getUserInfo response: " + response);
                postResponseToCallback(cb, response);
            }

            @Override
            public void onError(final KickflipException error) {
                Log.w(TAG, "getUserInfo Error: " + error);
                postExceptionToCallback(cb, error);
            }
        });
    }


    /**
     * Start a new Stream. Must be called after
     * {@link io.kickflip.sdk.api.KickflipApiClient#createNewUser(KickflipCallback)}
     * Delivers stream endpoint destination data via a {@link io.kickflip.sdk.api.KickflipCallback}.
     *
     * @param cb This callback will receive a Stream subclass in {@link io.kickflip.sdk.api.KickflipCallback#onSuccess(io.kickflip.sdk.api.json.Response)}
     *           depending on the Kickflip account type. Implementors should
     *           check if the response is instanceof HlsStream, RtmpStream, etc.
     */
    public void startStream(Stream stream, final KickflipCallback cb) {
        if (!assertActiveUserAvailable(cb)) return;
        checkNotNull(stream);
        startStreamWithUser(getActiveUser(), stream, cb);
    }

    /**
     * Start a new Stream owned by the given User. Must be called after
     * {@link io.kickflip.sdk.api.KickflipApiClient#createNewUser(KickflipCallback)}
     * Delivers stream endpoint destination data via a {@link io.kickflip.sdk.api.KickflipCallback}.
     *
     * @param user The Kickflip User on whose behalf this request is performed.
     * @param cb   This callback will receive a Stream subclass in {@link io.kickflip.sdk.api.KickflipCallback#onSuccess(io.kickflip.sdk.api.json.Response)}
     *             depending on the Kickflip account type. Implementors should
     *             check if the response is instanceof HlsStream, StartRtmpStreamResponse, etc.
     */
    private void startStreamWithUser(User user, Stream stream, final KickflipCallback cb) {
        checkNotNull(user);
        checkNotNull(stream);
        // TODO: Be HLS / RTMP Agnostic
        GenericData data = new GenericData();
        data.put("uuid", user.getUUID());
        data.put("private", stream.isPrivate());
        if (stream.getTitle() != null) {
            data.put("title", stream.getTitle());
        }
        if (stream.getDescription() != null) {
            data.put("description", stream.getDescription());
        }
        if (stream.getExtraInfo() != null) {
            data.put("extra_info", Jackson.toJsonString(stream.getExtraInfo()));
        }
        post(START_STREAM, new UrlEncodedContent(data), HlsStream.class, cb);
    }

    /**
     * Stop a Stream. Must be called after
     * {@link io.kickflip.sdk.api.KickflipApiClient#createNewUser(KickflipCallback)} and
     * {@link io.kickflip.sdk.api.KickflipApiClient#startStream(io.kickflip.sdk.api.json.Stream, KickflipCallback)}
     *
     * @param cb This callback will receive a Stream subclass in {@link io.kickflip.sdk.api.KickflipCallback#onSuccess(io.kickflip.sdk.api.json.Response)}
     *           depending on the Kickflip account type. Implementors should
     *           check if the response is instanceof HlsStream, StartRtmpStreamResponse, etc.
     */
    public void stopStream(Stream stream, final KickflipCallback cb) {
        if (!assertActiveUserAvailable(cb)) return;
        stopStream(getActiveUser(), stream, cb);
    }

    /**
     * Stop a Stream owned by the given Kickflip User.
     *
     * @param cb This callback will receive a Stream subclass in #onSuccess(response)
     *           depending on the Kickflip account type. Implementors should
     *           check if the response is instanceof HlsStream, StartRtmpStreamResponse, etc.
     */
    private void stopStream(User user, Stream stream, final KickflipCallback cb) {
        checkNotNull(stream);
        // TODO: Be HLS / RTMP Agnostic
        // TODO: Add start / stop lat lon to Stream?
        GenericData data = new GenericData();
        data.put("stream_id", stream.getStreamId());
        data.put("uuid", user.getUUID());
        if (stream.getLatitude() != 0) {
            data.put("lat", stream.getLatitude());
        }
        if (stream.getLongitude() != 0) {
            data.put("lon", stream.getLongitude());
        }
        post(STOP_STREAM, new UrlEncodedContent(data), HlsStream.class, cb);
    }

    /**
     * Send Stream Metadata for a {@link io.kickflip.sdk.api.json.Stream}.
     * The target Stream must be owned by the User created with {@link io.kickflip.sdk.api.KickflipApiClient#createNewUser(KickflipCallback)}
     * from this KickflipApiClient.
     *
     * @param stream the {@link io.kickflip.sdk.api.json.Stream} to get Meta data for
     * @param cb     A callback to receive the updated Stream upon request completion
     */
    public void setStreamInfo(Stream stream, final KickflipCallback cb) {
        if (!assertActiveUserAvailable(cb)) return;
        GenericData data = new GenericData();
        data.put("stream_id", stream.getStreamId());
        data.put("uuid", getActiveUser().getUUID());
        if (stream.getTitle() != null) {
            data.put("title", stream.getTitle());
        }
        if (stream.getDescription() != null) {
            data.put("description", stream.getDescription());
        }
        if (stream.getExtraInfo() != null) {
            data.put("extra_info", Jackson.toJsonString(stream.getExtraInfo()));
        }
        if (stream.getLatitude() != 0) {
            data.put("lat", stream.getLatitude());
        }
        if (stream.getLongitude() != 0) {
            data.put("lon", stream.getLongitude());
        }
        if (stream.getCity() != null) {
            data.put("city", stream.getCity());
        }
        if (stream.getState() != null) {
            data.put("state", stream.getState());
        }
        if (stream.getCountry() != null) {
            data.put("country", stream.getCountry());
        }

        if (stream.getThumbnailUrl() != null) {
            data.put("thumbnail_url", stream.getThumbnailUrl());
        }

        data.put("private", stream.isPrivate());
        data.put("deleted", stream.isDeleted());

        post(SET_META, new UrlEncodedContent(data), Stream.class, cb);
    }

    /**
     * Get Stream Metadata for a a public {@link io.kickflip.sdk.api.json.Stream}.
     * The target Stream must belong a User of your Kickflip app.
     *
     * @param stream the {@link io.kickflip.sdk.api.json.Stream} to get Meta data for
     * @param cb     A callback to receive the updated Stream upon request completion
     */
    public void getStreamInfo(Stream stream, final KickflipCallback cb) {
        GenericData data = new GenericData();
        data.put("stream_id", stream.getStreamId());

        post(GET_META, new UrlEncodedContent(data), Stream.class, cb);
    }

    /**
     * Get Stream Metadata for a a public {@link io.kickflip.sdk.api.json.Stream#mStreamId}.
     * The target Stream must belong a User within your Kickflip app.
     * <p/>
     * This method is useful when digesting a Kickflip.io/<stream_id> url, where only
     * the StreamId String is known.
     *
     * @param streamId the stream Id of the given stream. This is the value that appears
     *                 in urls of form kickflip.io/<stream_id>
     * @param cb       A callback to receive the current {@link io.kickflip.sdk.api.json.Stream} upon request completion
     */
    public void getStreamInfo(String streamId, final KickflipCallback cb) {
        GenericData data = new GenericData();
        data.put("stream_id", streamId);

        post(GET_META, new UrlEncodedContent(data), Stream.class, cb);
    }

    /**
     * Flag a {@link io.kickflip.sdk.api.json.Stream}. Used when the active Kickflip User does not own the Stream.
     * <p/>
     * To delete a recording the active Kickflip User owns, use
     * {@link io.kickflip.sdk.api.KickflipApiClient#setStreamInfo(io.kickflip.sdk.api.json.Stream, KickflipCallback)}
     *
     * @param stream The Stream to flag.
     * @param cb     A callback to receive the result of the flagging operation.
     */
    public void flagStream(Stream stream, final KickflipCallback cb) {
        if (!assertActiveUserAvailable(cb)) return;
        GenericData data = new GenericData();
        data.put("uuid", getActiveUser().getUUID());
        data.put("stream_id", stream.getStreamId());

        post(FLAG_STREAM, new UrlEncodedContent(data), Stream.class, cb);
    }

    /**
     * Get a List of {@link io.kickflip.sdk.api.json.Stream} objects created by the given Kickflip User.
     *
     * @param username the target Kickflip username
     * @param cb       A callback to receive the resulting List of Streams
     */
    public void getStreamsByUsername(String username, int pageNumber, int itemsPerPage, final KickflipCallback cb) {
        if (!assertActiveUserAvailable(cb)) return;
        GenericData data = new GenericData();
        addPaginationData(pageNumber, itemsPerPage, data);
        data.put("uuid", getActiveUser().getUUID());
        data.put("username", username);
        post(SEARCH_USER, new UrlEncodedContent(data), StreamList.class, cb);
    }

    /**
     * Get a List of {@link io.kickflip.sdk.api.json.Stream}s containing a keyword.
     * <p/>
     * This method searches all public recordings made by Users of your Kickflip app.
     *
     * @param keyword The String keyword to query
     * @param cb      A callback to receive the resulting List of Streams
     */
    public void getStreamsByKeyword(String keyword, int pageNumber, int itemsPerPage, final KickflipCallback cb) {
        if (!assertActiveUserAvailable(cb)) return;
        GenericData data = new GenericData();
        addPaginationData(pageNumber, itemsPerPage, data);
        data.put("uuid", getActiveUser().getUUID());
        if (keyword != null) {
            data.put("keyword", keyword);
        }
        post(SEARCH_KEYWORD, new UrlEncodedContent(data), StreamList.class, cb);
    }

    /**
     * Get a List of {@link io.kickflip.sdk.api.json.Stream}s near a geographic location.
     * <p/>
     * This method searches all public recordings made by Users of your Kickflip app.
     *
     * @param location The target Location
     * @param radius   The target Radius in meters
     * @param cb       A callback to receive the resulting List of Streams
     */
    public void getStreamsByLocation(Location location, int radius, int pageNumber, int itemsPerPage, final KickflipCallback cb) {
        if (!assertActiveUserAvailable(cb)) return;
        GenericData data = new GenericData();
        data.put("uuid", getActiveUser().getUUID());
        data.put("lat", location.getLatitude());
        data.put("lon", location.getLongitude());
        if (radius != 0) {
            data.put("radius", radius);
        }
        post(SEARCH_GEO, new UrlEncodedContent(data), StreamList.class, cb);
    }

    /**
     * Do a POST Request, creating a new user if necessary
     *
     * @param endpoint      Kickflip endpoint. e.g /user/new
     * @param responseClass Class of the expected response
     * @param cb            Callback that will receive an instance of responseClass
     */
    private void post(final String endpoint, final Class responseClass, final KickflipCallback cb) {
        post(endpoint, null, responseClass, cb);
    }

    /**
     * Do a POST Request, creating a new user if necessary
     *
     * @param endpoint      Kickflip endpoint. e.g /user/new
     * @param body          POST body
     * @param responseClass Class of the expected response
     * @param cb            Callback that will receive an instance of responseClass
     */
    private void post(final String endpoint, final HttpContent body, final Class responseClass, final KickflipCallback cb) {
        acquireAccessToken(new OAuthCallback() {
            @Override
            public void onSuccess(HttpRequestFactory requestFactory) {
                request(requestFactory, METHOD.POST, makeApiUrl(endpoint), body, responseClass, cb);
            }

            @Override
            public void onFailure(Exception e) {
                postExceptionToCallback(cb, UNKNOWN_ERROR_CODE);
            }
        });
    }

    private void request(HttpRequestFactory requestFactory, final METHOD method, final String url, final HttpContent content, final Class responseClass, final KickflipCallback cb) {
        if (VERBOSE)
            Log.i(TAG, String.format("REQUEST: %S : %s body: %s", method, shortenUrlString(url), (content == null ? "" : Jackson.toJsonPrettyString(content))));
        try {
            HttpRequest request = null;
            switch (method) {
                case GET:
                    request = requestFactory.buildGetRequest(
                            new GenericUrl(url)).setParser(getJsonObjectParser());
                    break;
                case POST:
                    request = requestFactory.buildPostRequest(
                            new GenericUrl(url), content).setParser(getJsonObjectParser());
                    break;
            }
            executeAndRetryRequest(request, responseClass, cb);
        } catch (final IOException exception) {
            // First try to handle as HttpResponseException
            try {
                HttpResponseException httpException = (HttpResponseException) exception;
                // If this cast suceeds, the HTTP Status code must be >= 300
                Log.i(TAG, "HttpException: " + httpException.getStatusCode());
                switch (httpException.getStatusCode()) {
                    case 403:
                        // OAuth Access Token invalid
                        Log.i(TAG, "Error 403: OAuth Token appears invalid. Clearing");
                        clearAccessToken();
                        acquireAccessToken(new OAuthCallback() {
                            @Override
                            public void onSuccess(HttpRequestFactory oauthRequestFactory) {
                                request(oauthRequestFactory, method, url, content, responseClass, cb);
                            }

                            @Override
                            public void onFailure(Exception e) {
                                postExceptionToCallback(cb, UNKNOWN_ERROR_CODE);
                            }
                        });
                        break;
                    case 400:
                        // Bad Client Credentials
                        Log.e(TAG, "Error 400: Check your Client key and secret");
                        break;
                    default:
                        Log.w(TAG, String.format("Unhandled Http Error %d : %s",
                                httpException.getStatusCode(),
                                httpException.getMessage()));
                }
                if (VERBOSE)
                    Log.i(TAG, "RESPONSE: " + shortenUrlString(url) + " " + exception.getMessage());
                postExceptionToCallback(cb, UNKNOWN_ERROR_CODE);
            } catch (ClassCastException e) {
                // A non-HTTP releated error occured.
                Log.w(TAG, String.format("Unhandled Error: %s. Stack trace follows:", e.getMessage()));
                exception.printStackTrace();
                postExceptionToCallback(cb, UNKNOWN_ERROR_CODE);
            }
        }
    }

    /**
     * Execute a HTTPRequest and retry up to {@link io.kickflip.sdk.api.KickflipApiClient#MAX_EOF_RETRIES} times if an EOFException occurs.
     * This is an attempt to address what appears to be a bug in NetHttpTransport
     * <p/>
     * See <a href="https://code.google.com/p/google-api-java-client/issues/detail?id=869&can=4&colspec=Milestone%20Priority%20Component%20Type%20Summary%20ID%20Status%20Owner">This issue</a>
     *
     * @param request
     * @param responseClass
     * @param cb
     * @throws IOException
     */
    private void executeAndRetryRequest(HttpRequest request, Class responseClass, KickflipCallback cb) throws IOException {
        int numRetries = 0;
        while (numRetries < MAX_EOF_RETRIES + 1) {
            try {
                executeAndHandleHttpRequest(request, responseClass, cb);
                // If executeAndHandleHttpRequest completes without throwing EOFException
                // we're good
                return;
            } catch (EOFException eof) {
                if (VERBOSE) Log.i(TAG, "Got EOFException. Retrying..");
                // An EOFException may be due to a bug in the way Connections are recycled
                // within the NetHttpTransport package. Ignore and retry
            }
            numRetries++;
        }
        postExceptionToCallback(cb, UNKNOWN_ERROR_CODE);
    }

    private void executeAndHandleHttpRequest(HttpRequest request, Class responseClass, KickflipCallback cb) throws IOException {
        handleHttpResponse(request.execute(), responseClass, cb);
    }

    /**
     * Verify HTTP response was successful
     * and pass to handleKickflipResponse.
     * <p/>
     * If we have an HttpResponse at all, it means
     * the status code was < 300, so as far as http inspection
     * goes, this method simply enforces status code of 200
     *
     * @param response
     * @param responseClass
     * @param cb            Must not be null
     * @throws IOException
     */
    private void handleHttpResponse(HttpResponse response, Class<? extends Response> responseClass, KickflipCallback cb) throws IOException {
        //Object parsedResponse = response.parseAs(responseClass);
        if (isSuccessResponse(response)) {
            // Http Success
            handleKickflipResponse(response, responseClass, cb);
            //cb.onSuccess(responseClass.cast(parsedResponse));
        } else {
            // Http Failure
            if (VERBOSE)
                Log.i(TAG, String.format("RESPONSE (F): %s body: %s", shortenUrlString(response.getRequest().getUrl().toString()), response.getContent().toString()));
            postExceptionToCallback(cb, UNKNOWN_ERROR_CODE);
        }
    }

    /**
     * Parse the HttpResponse as the appropriate Response subclass
     *
     * @param response
     * @param responseClass
     * @param cb
     * @throws IOException
     */
    private void handleKickflipResponse(HttpResponse response, Class<? extends Response> responseClass, KickflipCallback cb) throws IOException {
        if (cb == null) return;
        HashMap responseMap = null;
        Response kickFlipResponse = response.parseAs(responseClass);
        if (VERBOSE)
            Log.i(TAG, String.format("RESPONSE: %s body: %s", shortenUrlString(response.getRequest().getUrl().toString()), Jackson.toJsonPrettyString(kickFlipResponse)));
//        if (Stream.class.isAssignableFrom(responseClass)) {
//            if( ((String) responseMap.get("stream_type")).compareTo("HLS") == 0){
//                kickFlipResponse = response.parseAs(HlsStream.class);
//            } else if( ((String) responseMap.get("stream_type")).compareTo("RTMP") == 0){
//                // TODO:
//            }
//        } else if(User.class.isAssignableFrom(responseClass)){
//            kickFlipResponse = response.parseAs(User.class);
//        }
        if (kickFlipResponse == null) {
            postExceptionToCallback(cb, UNKNOWN_ERROR_CODE);
        } else if (!kickFlipResponse.isSuccessful()) {
            postExceptionToCallback(cb, UNKNOWN_ERROR_CODE);
        } else {
            postResponseToCallback(cb, kickFlipResponse);
        }
    }

    private void storeNewUserResponse(User response, String password) {
        getStorage().edit()
                .putString("app_name", response.getApp())
                .putString("name", response.getName())
                .putString("password", password)
                .putString("uuid", response.getUUID())
                .putString("uuid", response.getUUID())
                .apply();
    }

    private String getPasswordForActiveUser() {
        return getStorage().getString("password", null);
    }

    private boolean isUserCached() {
        //TODO: Ensure this use belongs to the current app
        return getStorage().contains("uuid");
    }

    private BasicAWSCredentials getAWSCredentials() {
        return new BasicAWSCredentials(
                getStorage().getString("aws_access_key", ""),
                getStorage().getString("aws_secret_key", ""));

    }

    /**
     * Get the current active Kickflip User. If no User has been created, returns null.
     * <p/>
     * This will be the User created on the last call to
     * {@link io.kickflip.sdk.api.KickflipApiClient#createNewUser(KickflipCallback)}
     *
     * @return
     */
    public User getActiveUser() {
        SharedPreferences prefs = getStorage();
        if (prefs.contains("uuid") && prefs.contains("name")) {
            return new User(
                    prefs.getString("app_name", ""),
                    prefs.getString("name", ""),
                    prefs.getString("uuid", ""),
                    null);
        } else {
            return null;
        }
    }

    private String getAWSBucket() {
        return getStorage().getString("app_name", "");
    }

    private JsonFactory getJsonFactory() {
        if (mJsonFactory == null)
            mJsonFactory = new JacksonFactory();
        return mJsonFactory;
    }

    private JsonObjectParser getJsonObjectParser() {
        if (mJsonObjectParser == null)
            mJsonObjectParser = new JsonObjectParser(getJsonFactory());
        return mJsonObjectParser;
    }

    private void postExceptionToCallback(final KickflipCallback cb, final int resourceCodeId) {
        final int errorCode = getContext().getResources().getInteger(resourceCodeId);
        final String message = getContext().getResources().getStringArray(R.array.error_messages)[errorCode];
        KickflipException error = new KickflipException(message, errorCode);
        postExceptionToCallback(cb, error);
    }

    private void postExceptionToCallback(final KickflipCallback cb, final KickflipException exception) {
        if (cb != null) {
            mCallbackHandler.post(new Runnable() {
                @Override
                public void run() {
                    cb.onError(exception);
                }
            });
        }
    }

    private void postResponseToCallback(final KickflipCallback cb, final Response response) {
        if (cb != null) {
            mCallbackHandler.post(new Runnable() {
                @Override
                public void run() {
                    cb.onSuccess(response);
                }
            });
        }
    }

    /**
     * Given a string like https://api.kickflip.io/api/search
     * return /api/search
     *
     * @param url
     * @return
     */
    private String shortenUrlString(String url) {
        return url.substring(BASE_URL.length());
    }

    /**
     * Check if a Stream is owned by the active Kickflip User.
     *
     * @param stream the Stream to test.
     * @return true if the active Kickflip User owns the Stream. false otherwise.
     */
    public boolean activeUserOwnsStream(Stream stream) {
        return getActiveUser().getName().compareTo(stream.getOwnerName()) == 0;
    }

    private boolean assertActiveUserAvailable(KickflipCallback cb) {
        if (getActiveUser() == null) {
            Log.e(TAG, "getStreamsByKeyword called before user acquired. If this request needs to be performed on app start," +
                    "call it from the KickflipCallback provided to setup()");
            if (cb != null) {
                postExceptionToCallback(cb, R.integer.user_not_available);
            }
            return false;
        }
        return true;
    }

    private static enum METHOD {GET, POST}

    static {
        if (DEV_ENDPOINT)
            BASE_URL = "http://funkcity.ngrok.com";
        else
            BASE_URL = "https://www.kickflip.io";
    }

    private String generateRandomPassword() {
        return new BigInteger(130, new SecureRandom()).toString(32);
    }

    private void addPaginationData(int pageNumber, int itemsPerPage, GenericData target) {
        target.put("results_per_page", itemsPerPage);
        target.put("page", pageNumber);
    }

    private String makeApiUrl(String endpoint) {
        return BASE_URL + "/api" + API_VERSION + endpoint;
    }

}




Java Source Code List

io.kickflip.sdk.FileUtils.java
io.kickflip.sdk.Kickflip.java
io.kickflip.sdk.Share.java
io.kickflip.sdk.Util.java
io.kickflip.sdk.activity.BroadcastActivity.java
io.kickflip.sdk.activity.GlassBroadcastActivity.java
io.kickflip.sdk.activity.ImmersiveActivity.java
io.kickflip.sdk.activity.MediaPlayerActivity.java
io.kickflip.sdk.api.KickflipApiClient.java
io.kickflip.sdk.api.KickflipCallback.java
io.kickflip.sdk.api.OAuthCallback.java
io.kickflip.sdk.api.OAuthClient.java
io.kickflip.sdk.api.OAuthConfig.java
io.kickflip.sdk.api.json.HlsStream.java
io.kickflip.sdk.api.json.Response.java
io.kickflip.sdk.api.json.StreamList.java
io.kickflip.sdk.api.json.Stream.java
io.kickflip.sdk.api.json.User.java
io.kickflip.sdk.api.s3.S3BroadcastManager.java
io.kickflip.sdk.api.s3.package-info.java
io.kickflip.sdk.av.AVRecorder.java
io.kickflip.sdk.av.AndroidEncoder.java
io.kickflip.sdk.av.AndroidMuxer.java
io.kickflip.sdk.av.AudioEncoderConfig.java
io.kickflip.sdk.av.AudioEncoderCore.java
io.kickflip.sdk.av.BroadcastListener.java
io.kickflip.sdk.av.Broadcaster.java
io.kickflip.sdk.av.CameraEncoder.java
io.kickflip.sdk.av.CameraSurfaceRenderer.java
io.kickflip.sdk.av.Drawable2d.java
io.kickflip.sdk.av.EglCore.java
io.kickflip.sdk.av.EglStateSaver.java
io.kickflip.sdk.av.EglSurfaceBase.java
io.kickflip.sdk.av.FFmpegMuxer.java
io.kickflip.sdk.av.Filters.java
io.kickflip.sdk.av.FullFrameRect.java
io.kickflip.sdk.av.GlUtil.java
io.kickflip.sdk.av.HlsFileObserver.java
io.kickflip.sdk.av.M3u8Parser.java
io.kickflip.sdk.av.MicrophoneEncoder.java
io.kickflip.sdk.av.Muxer.java
io.kickflip.sdk.av.SessionConfig.java
io.kickflip.sdk.av.SizeableFrameRect.java
io.kickflip.sdk.av.Texture2dProgram.java
io.kickflip.sdk.av.VideoEncoderConfig.java
io.kickflip.sdk.av.VideoEncoderCore.java
io.kickflip.sdk.av.WindowSurface.java
io.kickflip.sdk.event.BroadcastEvent.java
io.kickflip.sdk.event.BroadcastIsBufferingEvent.java
io.kickflip.sdk.event.BroadcastIsLiveEvent.java
io.kickflip.sdk.event.CameraOpenedEvent.java
io.kickflip.sdk.event.HlsManifestUploadedEvent.java
io.kickflip.sdk.event.HlsManifestWrittenEvent.java
io.kickflip.sdk.event.HlsSegmentUploadedEvent.java
io.kickflip.sdk.event.HlsSegmentWrittenEvent.java
io.kickflip.sdk.event.MuxerFinishedEvent.java
io.kickflip.sdk.event.S3FailedUploadEvent.java
io.kickflip.sdk.event.S3UploadEvent.java
io.kickflip.sdk.event.StreamLocationAddedEvent.java
io.kickflip.sdk.event.ThumbnailWrittenEvent.java
io.kickflip.sdk.event.UploadEvent.java
io.kickflip.sdk.event.package-info.java
io.kickflip.sdk.exception.KickflipException.java
io.kickflip.sdk.fragment.BroadcastFragment.java
io.kickflip.sdk.fragment.GlassBroadcastFragment.java
io.kickflip.sdk.fragment.MediaPlayerFragment.java
io.kickflip.sdk.fragment.package-info.java
io.kickflip.sdk.location.DeviceLocation.java
io.kickflip.sdk.location.package-info.java
io.kickflip.sdk.view.GLCameraEncoderView.java
io.kickflip.sdk.view.GLCameraView.java
io.kickflip.sdk.view.package-info.java
pro.dbro.ffmpegwrapper.FFmpegWrapper.java
pro.dbro.ffmpegwrapper.package-info.java