com.norman0406.slimgress.API.Interface.Interface.java Source code

Java tutorial

Introduction

Here is the source code for com.norman0406.slimgress.API.Interface.Interface.java

Source

/***********************************************************************
*
* Slimgress: Ingress API for Android
* Copyright (C) 2013 Norman Link <norman.link@gmx.net>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program.  If not, see <http://www.gnu.org/licenses/>.
*
***********************************************************************/

package com.norman0406.slimgress.API.Interface;

import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.Date;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
import java.util.zip.GZIPInputStream;

import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.params.ClientPNames;
import org.apache.http.cookie.Cookie;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.util.EntityUtils;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import com.norman0406.slimgress.API.Common.Location;

import android.os.Build;
import android.util.Log;

public class Interface {
    public enum AuthSuccess {
        Successful, TokenExpired, UnknownError
    }

    private DefaultHttpClient mClient;
    private String mCookie;

    // ingress api definitions
    private static final String mApiVersion = "2013-08-07T00:06:39Z a52083df5202 opt";
    private static final String mApiBase = "m-dot-betaspike.appspot.com";
    private static final String mApiBaseURL = "https://" + mApiBase + "/";
    private static final String mApiLogin = "_ah/login?continue=http://localhost/&auth=";
    private static final String mApiHandshake = "handshake?json=";
    private static final String mApiRequest = "rpc/";

    public Interface() {
        mClient = new DefaultHttpClient();
    }

    public AuthSuccess authenticate(final String token) {
        FutureTask<AuthSuccess> future = new FutureTask<AuthSuccess>(new Callable<AuthSuccess>() {
            @Override
            public AuthSuccess call() throws Exception {
                // see http://blog.notdot.net/2010/05/Authenticating-against-App-Engine-from-an-Android-app
                // also use ?continue= (?)

                String login = mApiBaseURL + mApiLogin + token;
                HttpGet get = new HttpGet(login);

                try {
                    HttpResponse response = null;
                    synchronized (Interface.this) {
                        mClient.getParams().setBooleanParameter(ClientPNames.HANDLE_REDIRECTS, false);
                        Log.i("Interface", "executing authentication");
                        response = mClient.execute(get);
                    }
                    assert (response != null);
                    @SuppressWarnings("unused")
                    String content = EntityUtils.toString(response.getEntity());
                    response.getEntity().consumeContent();

                    if (response.getStatusLine().getStatusCode() == 401) {
                        // the token has expired
                        Log.i("Interface", "401: authentication token has expired");
                        return AuthSuccess.TokenExpired;
                    } else if (response.getStatusLine().getStatusCode() != 302) {
                        // Response should be a redirect
                        Log.i("Interface", "unknown error: " + response.getStatusLine().getReasonPhrase());
                        return AuthSuccess.UnknownError;
                    } else {
                        // get cookie
                        synchronized (Interface.this) {
                            for (Cookie cookie : mClient.getCookieStore().getCookies()) {
                                if (cookie.getName().equals("SACSID")) { // secure cookie! (ACSID is non-secure http cookie)
                                    mCookie = cookie.getValue();
                                }
                            }
                        }

                        if (mCookie == null) {
                            Log.i("Interface", "authentication token has expired");
                            return AuthSuccess.TokenExpired;
                        }

                        Log.i("Interface", "authentication successful");
                        return AuthSuccess.Successful;
                    }
                } catch (ClientProtocolException e) {
                    e.printStackTrace();
                } catch (IOException e) {
                    e.printStackTrace();
                } finally {
                    synchronized (Interface.this) {
                        mClient.getParams().setBooleanParameter(ClientPNames.HANDLE_REDIRECTS, true);
                    }
                }
                return AuthSuccess.Successful;
            }
        });

        // start thread
        new Thread(future).start();

        // obtain authentication return value
        AuthSuccess retVal = AuthSuccess.UnknownError;
        try {
            retVal = future.get();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }

        return retVal;
    }

    public void handshake(final Handshake.Callback callback) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                JSONObject params = new JSONObject();
                try {
                    // set handshake parameters
                    params.put("nemesisSoftwareVersion", mApiVersion);
                    params.put("deviceSoftwareVersion", Build.VERSION.RELEASE);

                    // TODO:
                    /*params.put("activationCode", "");
                    params.put("tosAccepted", "1");
                    params.put("a", "");*/

                    String paramString = params.toString();
                    paramString = URLEncoder.encode(paramString, "UTF-8");

                    String handshake = mApiBaseURL + mApiHandshake + paramString;

                    HttpGet get = new HttpGet(handshake);
                    get.setHeader("Accept-Charset", "utf-8");
                    get.setHeader("Cache-Control", "max-age=0");

                    // do handshake
                    HttpResponse response = null;
                    synchronized (Interface.this) {
                        Log.i("Interface", "executing handshake");
                        response = mClient.execute(get);
                    }
                    assert (response != null);
                    HttpEntity entity = response.getEntity();

                    if (entity != null) {
                        String content = EntityUtils.toString(entity);
                        Header contentType = entity.getContentType();
                        entity.consumeContent();

                        // check for content type json
                        if (!contentType.getName().equals("Content-Type")
                                || !contentType.getValue().contains("application/json"))
                            throw new RuntimeException("content type is not json");

                        content = content.replace("while(1);", "");

                        // handle handshake data
                        callback.handle(new Handshake(new JSONObject(content)));

                        Log.i("Interface", "handshake finished");
                    }
                } catch (ClientProtocolException e) {
                    e.printStackTrace();
                } catch (IOException e) {
                    e.printStackTrace();
                } catch (JSONException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }

    public void request(final Handshake handshake, final String requestString, final Location playerLocation,
            final JSONObject requestParams, final RequestResult result) throws InterruptedException {
        if (!handshake.isValid() || handshake.getXSRFToken().length() == 0)
            throw new RuntimeException("handshake is not valid");

        new Thread(new Runnable() {
            public void run() {

                // create post
                String postString = mApiBaseURL + mApiRequest + requestString;
                HttpPost post = new HttpPost(postString);

                // set additional parameters
                JSONObject params = new JSONObject();
                if (requestParams != null) {
                    if (requestParams.has("params"))
                        params = requestParams;
                    else {
                        try {
                            params.put("params", requestParams);

                            // add persistent request parameters
                            if (playerLocation != null) {
                                String loc = String.format("%08x,%08x", playerLocation.getLatitude(),
                                        playerLocation.getLongitude());
                                params.getJSONObject("params").put("playerLocation", loc);
                                params.getJSONObject("params").put("location", loc);
                            }
                            params.getJSONObject("params").put("knobSyncTimestamp", getCurrentTimestamp());

                            JSONArray collectedEnergy = new JSONArray();

                            // TODO: add collected energy guids

                            params.getJSONObject("params").put("energyGlobGuids", collectedEnergy);
                        } catch (JSONException e) {
                            e.printStackTrace();
                        }
                    }
                } else {
                    try {
                        params.put("params", null);
                    } catch (JSONException e) {
                        e.printStackTrace();
                    }
                }

                try {
                    StringEntity entity = new StringEntity(params.toString(), "UTF-8");
                    entity.setContentType("application/json");
                    post.setEntity(entity);
                } catch (UnsupportedEncodingException e) {
                    e.printStackTrace();
                }

                // set header
                post.setHeader("Content-Type", "application/json;charset=UTF-8");
                post.setHeader("Accept-Encoding", "gzip");
                post.setHeader("User-Agent", "Nemesis (gzip)");
                post.setHeader("X-XsrfToken", handshake.getXSRFToken());
                post.setHeader("Host", mApiBase);
                post.setHeader("Connection", "Keep-Alive");
                post.setHeader("Cookie", "SACSID=" + mCookie);

                // execute and get the response.
                try {
                    HttpResponse response = null;
                    String content = null;

                    synchronized (Interface.this) {
                        response = mClient.execute(post);
                        assert (response != null);

                        if (response.getStatusLine().getStatusCode() == 401) {
                            // token expired or similar
                            //isAuthenticated = false;
                            response.getEntity().consumeContent();
                        } else {
                            HttpEntity entity = response.getEntity();

                            // decompress gzip if necessary
                            Header contentEncoding = entity.getContentEncoding();
                            if (contentEncoding != null && contentEncoding.getValue().equalsIgnoreCase("gzip"))
                                content = decompressGZIP(entity);
                            else
                                content = EntityUtils.toString(entity);

                            entity.consumeContent();
                        }
                    }

                    // handle request result
                    if (content != null) {
                        JSONObject json = new JSONObject(content);
                        RequestResult.handleRequest(json, result);
                    }
                } catch (ClientProtocolException e) {
                    e.printStackTrace();
                } catch (IOException e) {
                    e.printStackTrace();
                } catch (JSONException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }

    private static String decompressGZIP(HttpEntity compressedEntity) throws IOException {
        final int bufferSize = 8192;
        InputStream input = compressedEntity.getContent();
        GZIPInputStream gzipStream = new GZIPInputStream(input, bufferSize);
        StringBuilder string = new StringBuilder();
        byte[] data = new byte[bufferSize];
        int bytesRead;
        while ((bytesRead = gzipStream.read(data)) != -1) {
            string.append(new String(data, 0, bytesRead));
        }
        gzipStream.close();
        input.close();
        return string.toString();
    }

    private long getCurrentTimestamp() {
        return (new Date()).getTime();
    }
}