com.aniruddhc.acemusic.player.GMusicHelpers.GMusicClientCalls.java Source code

Java tutorial

Introduction

Here is the source code for com.aniruddhc.acemusic.player.GMusicHelpers.GMusicClientCalls.java

Source

/*
 * Copyright (C) 2014 Saravan Pantham
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.aniruddhc.acemusic.player.GMusicHelpers;

import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;

import org.apache.http.client.HttpClient;
import org.apache.http.cookie.Cookie;
import org.apache.http.entity.ByteArrayEntity;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import android.content.Context;
import android.text.TextUtils;

import com.loopj.android.http.PersistentCookieStore;
import com.loopj.android.http.RequestParams;

/*******************************************************************************
 * This class contains the MobileClient (preferred) and WebClient (deprecated) 
 * endpoints and method calls for Google Play Music. Retrieving the song stream 
 * URL and reordering songs in a playlist are the only operations that should 
 * be carried out using the WebClient protocol.
 * 
 * @author Saravan Pantham
 *******************************************************************************/
public class GMusicClientCalls {

    public static Context mContext;
    private static PersistentCookieStore mCookieStore;
    public static GMusicHttpClient mHttpClient;
    private static GMusicClientCalls mInstance;
    private static String mWebClientUserAgent;
    private static String mAuthToken;
    private static String mMobileClientUserAgent = "Android-Music/1301 (t03g JDQ39); gzip";
    public static JSONArray mPlaylistEntriesMutationsArray = new JSONArray();

    public static void createInstance(Context context) {
        getInstance(context);
    }

    public static GMusicClientCalls getInstance(Context context) {
        if (mInstance == null)
            mInstance = new GMusicClientCalls(context);

        return mInstance;
    }

    private GMusicClientCalls(Context context) {
        mHttpClient = new GMusicHttpClient();
        mContext = context;
        mCookieStore = new PersistentCookieStore(context.getApplicationContext());
        mHttpClient.setCookieStore(mCookieStore);
        mHttpClient.setUserAgent("");
    }

    /*************************************************
     * Returns the raw HTTP client behind the custom 
     * GMusicHTTPClient implementation.
     * @return The raw HTTP client object.
     *************************************************/
    public static final HttpClient getRawHttpClient() {
        return mHttpClient.getHttpClient();
    }

    /******************************************************************
     * Sets the HTTP client's authorization header using the specified 
     * authentication token. The header will be in the following form: 
     * 
     * "Authorization", "GoogleLogin auth=xxxxxxxxxxxxxx",
     * 
     * where "xxxxxxxxxxxxxxx" is the authentication token.
     * 
     * @param authToken The authentication token that will be used to 
     * set the header.
     ******************************************************************/
    public static final void setAuthorizationHeader(String authToken) {
        mAuthToken = authToken;
        mHttpClient.addHeader("Authorization", "GoogleLogin auth=" + mAuthToken);
    }

    /************************************************************************
     * Resets the current HTTP client object by shutting it down and then 
     * reinstantiating it. The old cookie store, user agent and authorization 
     * header will be reused. This process will be done on a separate thread.
     ************************************************************************/
    public static void resetHttpClient() {
        //Reset the HTTP Client on a separate thread.
        Thread thread = new Thread() {

            @Override
            public void run() {
                if (mHttpClient != null) {
                    mHttpClient.getHttpClient().getConnectionManager().shutdown();
                    mHttpClient = new GMusicHttpClient();
                    mHttpClient.setCookieStore(mCookieStore);
                    mHttpClient.setUserAgent(mWebClientUserAgent);
                    mHttpClient.addHeader("Authorization", "GoogleLogin auth=" + mAuthToken);
                }

            }

        };
        thread.start();

    }

    /*********************************************************
     * Sets the user agent for webclient calls.
     * 
     * @param userAgent
     *********************************************************/
    public static final void setWebClientUserAgent(String userAgent) {
        mWebClientUserAgent = userAgent;
        mHttpClient.setUserAgent(mWebClientUserAgent);
    }

    public static final GMusicHttpClient getHttpClient() {
        return mHttpClient;
    }

    /*************************************************
     * Loops through the HTTP client's cookie store 
     * and returns the value of the "xt" cookie.
     * 
     * @return Value of the "xt" cookie.
     *************************************************/
    private static final String getXtCookieValue() {

        for (Cookie cookie : mCookieStore.getCookies()) {
            if (cookie.getName().equals("xt"))
                return cookie.getValue();
        }

        return null;
    }

    /*******************************************************************************
     * Attempts to log the user into the "sj" (SkyJam) service using the provided
     * authentication token. The authentication token is unique for each session 
     * and user account. It can be obtained via the GoogleAuthUtil.getToken() 
     * method. See AsyncGoogleMusicAuthenticationTask.java for the current
     * implementation of this process. This method will return true if the login
     * process succeeded. Returns false for any other type of failure.
     * 
     * @param context The context that will be used for the login process.
     * @param authToken The authentication token that will be used to login. 
     *******************************************************************************/
    public static final boolean login(Context context, String authToken) {

        if (!TextUtils.isEmpty(authToken)) {
            JSONForm form = new JSONForm().close();
            GMusicClientCalls.setAuthorizationHeader(authToken);
            String response = mHttpClient.post(context, "https://play.google.com/music/listen?hl=en&u=0",
                    new ByteArrayEntity(form.toString().getBytes()), form.getContentType());

            //Check if the required paramters are null.
            if (response != null) {

                if (getXtCookieValue() != null) {
                    return true;
                } else {
                    return false;
                }

            } else {
                return false;
            }

        } else {
            return false;
        }

    }

    /*****************************************************************
     * Gets the URI of the song stream using the specified song Id. 
     * The URI is dynamically generated by Google's servers and 
     * expires after one minute of no usage. This method uses the 
     * WebClient endpoint.
     * 
     * @param songId The id of the song that needs to be streamed.
     * @return Returns the URI of the song stream.
     * @throws JSONException
     * @throws URISyntaxException
     ******************************************************************/
    public static final URI getSongStream(String songId) throws JSONException, URISyntaxException {

        RequestParams params = new RequestParams();
        params.put("u", "0");
        params.put("songid", songId);
        params.put("pt", "e");

        String response = mHttpClient.get("https://play.google.com/music/play", params);

        if (response != null) {
            JSONObject jsonObject = new JSONObject(response);
            return new URI(jsonObject.optString("url", null));
        }

        return null;
    }

    /***************************************************************************************
     * @deprecated The use of this method is highly discouraged as it sends/fetches large 
     * amounts of data from Google's servers. All of this data is readily available via the 
     * Google Play Music app's public ContentProvider. This method uses the WebClient 
     * endpoint and is a helper method for getSongs().
     * 
     * @param context The context to use during the download process.
     * @return
     * @throws JSONException
     ***************************************************************************************/
    public static final ArrayList<WebClientSongsSchema> getAllSongs(Context context) throws JSONException {
        return getSongs(context, "");
    }

    /***************************************************************************************
     * <p>
     * Queries Google's servers for a list of all songs in the current Google account's 
     * music library.
     * </p>
     * 
     * @deprecated The use of this method is highly discouraged as it sends/fetches large 
     * amounts of data from Google's servers. All of this data is readily available via the 
     * Google Play Music app's public ContentProvider. This method uses the WebClient 
     * endpoint.
     * 
     * @param context The context to use during the download process.
     * @param continuationToken The token that will return the next set of songs (only 1000 
     * songs are returned per request).
     * @return
     * @throws JSONException
     ***************************************************************************************/
    public static final ArrayList<WebClientSongsSchema> getSongs(Context context, String continuationToken)
            throws JSONException {

        JSONForm form = new JSONForm();
        form.addField("json", "{\"continuationToken\":\"" + continuationToken + "\"}");
        form.close();

        String response = mHttpClient.post(context,
                "https://play.google.com/music/services/loadalltracks?u=0&xt=" + getXtCookieValue(),
                new ByteArrayEntity(form.toString().getBytes()), form.getContentType());

        JSONObject jsonObject = new JSONObject(response);
        WebClientPlaylistsSchema playlist = new WebClientPlaylistsSchema().fromJsonObject(jsonObject);

        ArrayList<WebClientSongsSchema> chunkedSongList = new ArrayList<WebClientSongsSchema>();
        chunkedSongList.addAll(playlist.getPlaylist());

        if (!TextUtils.isEmpty(playlist.getContinuationToken())) {
            chunkedSongList.addAll(getSongs(context, playlist.getContinuationToken()));
        }

        return chunkedSongList;
    }

    /****************************************************************************
     * Creates a new, user generated playlist. This method only creates the 
     * playlist; it does not add songs to the playlist.
     * 
     * @param context The context to use while creating the new playlist.
     * @param playlistName The name of the new playlist.
     * @return Returns the playlistId of the newly created playlist.
     * @throws JSONException
     * @throws IllegalArgumentException
     ****************************************************************************/
    public final static String createPlaylist(Context context, String playlistName)
            throws JSONException, IllegalArgumentException {

        JSONObject jsonParam = new JSONObject();
        JSONArray mutationsArray = new JSONArray();
        JSONObject createObject = new JSONObject();

        createObject.put("lastModifiedTimestamp", "0");
        createObject.put("name", playlistName);
        createObject.put("creationTimestamp", "-1");
        createObject.put("type", "USER_GENERATED");
        createObject.put("deleted", false);

        mutationsArray.put(new JSONObject().put("create", createObject));
        jsonParam.put("mutations", mutationsArray);

        mHttpClient.setUserAgent(mMobileClientUserAgent);
        String result = mHttpClient.post(context,
                "https://www.googleapis.com/sj/v1.1/playlistbatch?alt=json&hl=en_US",
                new ByteArrayEntity(jsonParam.toString().getBytes()), "application/json");

        mHttpClient.setUserAgent(mWebClientUserAgent);
        return new JSONObject(result).optJSONArray("mutate_response").getJSONObject(0).optString("id");
    }

    /*****************************************************************************
     * Creates a JSONObject object that contains the delete command for the 
     * specified playlist and adds it to the JSONArray that will pass the the 
     * command on to Google's servers. 
     * 
     * @param context The context to use while deleting the playlist.
     * @param playlistId The playlistId of the playlist to delete.
     * @throws JSONException
     * @throws IllegalArgumentException
     *****************************************************************************/
    public static final String deletePlaylist(Context context, String playlistId)
            throws JSONException, IllegalArgumentException {

        JSONObject jsonParam = new JSONObject();
        JSONArray mutationsArray = new JSONArray();

        mutationsArray.put(new JSONObject().put("delete", playlistId));
        jsonParam.put("mutations", mutationsArray);

        mHttpClient.setUserAgent(mMobileClientUserAgent);
        String result = mHttpClient.post(context,
                "https://www.googleapis.com/sj/v1.1/playlistbatch?alt=json&hl=en_US",
                new ByteArrayEntity(jsonParam.toString().getBytes()), "application/json");

        mHttpClient.setUserAgent(mWebClientUserAgent);
        return result;
    }

    /*****************************************************************************
     * Creates a JSONObject object that contains the delete command for the 
     * specified playlist entry.The object will be added to the JSONArray that 
     * will be passed on to Google's servers. 
     * 
     * @param playlistId The playlistId of the playlist to delete.
     * @throws JSONException
     * @throws IllegalArgumentException
     *****************************************************************************/
    public static final void putDeletePlaylistEntryRequest(String playlistEntryId)
            throws JSONException, IllegalArgumentException {

        JSONObject deleteObject = new JSONObject();
        deleteObject.put("delete", playlistEntryId);
        mPlaylistEntriesMutationsArray.put(deleteObject);
    }

    /*****************************************************************************
     * Adds the specified JSONObject to mPlaylistEntriesMutationsArray. The added 
     * JSONObject will be placed under the "create" key. The JSONObject should 
     * contain valid info about the new playlist entry (song) that will be created.
     * 
     * @param createObject The JSONObject that contains the new playlist entry's 
     * info and will be placed under the "create" key. 
     *****************************************************************************/
    public static final void putCreatePlaylistEntryRequest(JSONObject createObject) throws JSONException {
        mPlaylistEntriesMutationsArray.put(new JSONObject().put("create", createObject));
    }

    /*****************************************************************************
     * Adds the specified JSONObject to mPlaylistEntriesMutationsArray. The added 
     * JSONObject will be placed under the "update" key. The JSONObject should 
     * contain valid info about the playlist entry that is being updated.
     * 
     * @param updateObject The JSONObject that contains the updated playlist entry's 
     * info and will be placed under the "update" key. 
     *****************************************************************************/
    public static final void putUpdatePlaylistEntryRequest(JSONObject updateObject) throws JSONException {
        mPlaylistEntriesMutationsArray.put(new JSONObject().put("update", updateObject));
    }

    /******************************************************************************************
     * Executes a single/batch modification operation on a playlist's entry(ies). This method 
     * is a general purpose method that simply hits the MobileClient endpoints using
     * mPlaylistEntriesMutationsArray. Supported mutation operations include "create", 
     * "delete", and "update". 
     * 
     * @param context The context to use while carrying out the modification operation.
     * @param mutationsArray The JSONArray that contains the mutations command to be 
     * carried out.
     * @return The JSON response as a String.
     * @throws JSONException
     * @throws IllegalArgumentException
     ******************************************************************************************/
    public static final String modifyPlaylist(Context context) throws JSONException, IllegalArgumentException {

        JSONObject jsonParam = new JSONObject();
        jsonParam.put("mutations", mPlaylistEntriesMutationsArray);

        mHttpClient.setUserAgent(mMobileClientUserAgent);
        String result = mHttpClient.post(context,
                "https://www.googleapis.com/sj/v1.1/plentriesbatch?alt=json&hl=en_US",
                new ByteArrayEntity(jsonParam.toString().getBytes()), "application/json");

        mHttpClient.setUserAgent(mWebClientUserAgent);

        //Clear out and reset the mutationsArray now that we're done using it.
        mPlaylistEntriesMutationsArray = null;
        mPlaylistEntriesMutationsArray = new JSONArray();

        return result;
    }

    /*******************************************************************************************
     * Returns the number of elements in mPlaylistEntriesMutationsArray. Used to check if a 
     * POST request should be sent to Google's servers.
     *******************************************************************************************/
    public static int getQueuedMutationsCount() {
        return mPlaylistEntriesMutationsArray.length();
    }

    /*******************************************************************************************
     * Sends a POST request to Google's servers and retrieves a JSONArray with all user 
     * playlists. The JSONArray contains the fields of the playlist such as "id", "name", 
     * "type", etc. (for a list of all response fields, see MobileClientPlaylistsSchema.java).
     * 
     * @return A JSONArray object that contains all user playlists and their fields.
     * @param context The context to use while retrieving user playlists.
     *******************************************************************************************/
    public static final JSONArray getUserPlaylistsMobileClient(Context context)
            throws JSONException, IllegalArgumentException {

        JSONObject jsonRequestParams = new JSONObject();
        JSONArray playlistsJSONArray = new JSONArray();

        jsonRequestParams.put("max-results", 250);
        jsonRequestParams.put("start-token", "0");

        mHttpClient.setUserAgent(mMobileClientUserAgent);
        String result = mHttpClient.post(context,
                "https://www.googleapis.com/sj/v1.1/playlistfeed?alt=json&hl=en_US&tier=basic",
                new ByteArrayEntity(jsonRequestParams.toString().getBytes()), "application/json");

        JSONObject resultJSONObject = new JSONObject(result);
        JSONObject dataJSONObject = new JSONObject();

        if (resultJSONObject != null) {
            dataJSONObject = resultJSONObject.optJSONObject("data");
        }

        if (dataJSONObject != null) {
            playlistsJSONArray = dataJSONObject.getJSONArray("items");
        }

        return playlistsJSONArray;
    }

    /******************************************************************************************
     * Retrieves a JSONAray with all songs in <i><b>every</b></i> playlist. The JSONArray 
     * contains the fields of the songs such as "id", "clientId", "trackId", etc. (for a list 
     * of all fields, see MobileClientPlaylistEntriesSchema.java). 
     * 
     * @deprecated This method is fully functional. However, there are issues with retrieving 
     * the correct playlist entryIds. Specifically, the entryIds do not seem to work with 
     * reordering playlists via the MobileClient mutations protocol. 
     * 
     * @return A JSONArray object that contains all songs and their fields within every playlist. 
     * @param context The context to use while retrieving songs from the playlist.
     ******************************************************************************************/
    public static final JSONArray getPlaylistEntriesMobileClient(Context context)
            throws JSONException, IllegalArgumentException {

        JSONArray playlistEntriesJSONArray = new JSONArray();
        JSONObject jsonRequestParams = new JSONObject();

        jsonRequestParams.put("max-results", 10000);
        jsonRequestParams.put("start-token", "0");

        mHttpClient.setUserAgent(mMobileClientUserAgent);
        String result = mHttpClient.post(context,
                "https://www.googleapis.com/sj/v1.1/plentryfeed?alt=json&hl=en_US&tier=basic",
                new ByteArrayEntity(jsonRequestParams.toString().getBytes()), "application/json");

        JSONObject resultJSONObject = new JSONObject(result);
        JSONObject dataJSONObject = new JSONObject();

        if (resultJSONObject != null) {
            dataJSONObject = resultJSONObject.optJSONObject("data");
        }

        if (dataJSONObject != null) {
            playlistEntriesJSONArray = dataJSONObject.getJSONArray("items");
        }

        return playlistEntriesJSONArray;
    }

    /**************************************************************************************************
     * Retrieves a JSONAray with all songs within the <b><i>specified</b></i> playlist. The JSONArray 
     * contains the fields of the songs such as "id", "clientId", "trackId", etc. (for a list 
     * of all fields, see WebClientSongsSchema.java). Uses the WebClient endpoint.
     * 
     * @return A JSONArray object that contains the songs and their fields within the specified playlist.
     * @param context The context to use while retrieving songs from the playlist.
     * @param playlistId The id of the playlist we need to fetch the songs from.
     **************************************************************************************************/
    public static final JSONArray getPlaylistEntriesWebClient(Context context, String playlistId)
            throws JSONException, IllegalArgumentException {

        JSONObject jsonParam = new JSONObject();
        jsonParam.putOpt("id", playlistId);

        JSONForm form = new JSONForm();
        form.addField("json", jsonParam.toString());
        form.close();

        mHttpClient.setUserAgent(mMobileClientUserAgent);
        String result = mHttpClient.post(context,
                "https://play.google.com/music/services/loadplaylist?u=0&xt=" + getXtCookieValue(),
                new ByteArrayEntity(form.toString().getBytes()), form.getContentType());

        JSONArray jsonArray = new JSONArray();
        JSONObject jsonObject = new JSONObject(result);

        if (jsonObject != null) {
            jsonArray = jsonObject.getJSONArray("playlist");
        }

        return jsonArray;
    }

    /**************************************************************************************
     * Reorders the specified song (within the specified playlist) to a new position.
     * 
     * @param context The context to use during the reordering process.
     * @param playlistId The id of the playlist which contains the song to be reordered.
     * @param movedSongId The id of the song that is being reordered.
     * @param movedEntryId The entryId of the song that is being reordered.
     * @param afterEntryId The entryId of the song that is before the new position.
     * @param beforeEntryId The entryId of the song that is after the new position.
     * @return Returns the JSON response of the reorder task.
     * @throws JSONException
     **************************************************************************************/
    public static final String reorderPlaylistEntryWebClient(Context context, String playlistId,
            ArrayList<String> movedSongId, ArrayList<String> movedEntryId, String afterEntryId,
            String beforeEntryId) throws JSONException {

        JSONObject jsonParam = new JSONObject();
        jsonParam.put("playlistId", playlistId);
        jsonParam.put("movedSongIds", movedSongId);
        jsonParam.put("movedEntryIds", movedEntryId);
        jsonParam.put("afterEntryId", afterEntryId);
        jsonParam.put("beforeEntryId", beforeEntryId);

        String jsonParamString = jsonParam.toString();
        jsonParamString = jsonParamString.replace("\"[", "[\"");
        jsonParamString = jsonParamString.replace("]\"", "\"]");

        JSONForm form = new JSONForm();
        form.addField("json", jsonParamString);
        form.close();

        String result = mHttpClient.post(context,
                "https://play.google.com/music/services/changeplaylistorder?u=0&xt=" + getXtCookieValue(),
                new ByteArrayEntity(form.toString().getBytes()), form.getContentType());

        return result;
    }

}