com.meiste.greg.ptw.GAE.java Source code

Java tutorial

Introduction

Here is the source code for com.meiste.greg.ptw.GAE.java

Source

/*
 * Copyright (C) 2012-2014 Gregory S. Meiste  <http://gregmeiste.com>
 *
 * 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.meiste.greg.ptw;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URI;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;

import org.apache.http.Header;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.params.HttpClientParams;
import org.apache.http.cookie.Cookie;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicHeader;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;
import org.apache.http.protocol.HTTP;

import com.meiste.greg.ptw.tab.Questions;
import com.meiste.greg.ptw.tab.Standings;

import android.accounts.Account;
import android.accounts.AccountManager;
import android.accounts.AccountManagerCallback;
import android.accounts.AccountManagerFuture;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.preference.PreferenceManager;

public final class GAE {

    public static final String PROD_URL = "https://ptwgame.appspot.com";
    public static final String ACCOUNT_TYPE = "com.google";

    private static final String AUTH_COOKIE_NAME = "SACSID";
    // Timeout in milliseconds until a connection is established.
    private static final int TIMEOUT_CONNECTION = 10000;
    // Timeout in milliseconds to wait for data.
    private static final int TIMEOUT_SOCKET = 15000;

    private static final Object sInstanceSync = new Object();
    private static GAE sInstance;

    private final Context mContext;
    private final Handler mHandler;
    private final Object mListenerSync = new Object();
    private GaeListener mListener;
    private String mAccountName;
    private boolean mNeedInvalidate = true;
    private int mRemainingRetries = 3;
    private String mGetPage;
    private String mJson;

    public static interface GaeListener {
        void onFailedConnect(Context context);

        void onLaunchIntent(Intent launch);

        void onConnectSuccess(Context context, String json);

        void onGet(Context context, String json);
    }

    public static boolean isAccountSetupNeeded(final Context context) {
        final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
        final String account = prefs.getString(EditPreferences.KEY_ACCOUNT_EMAIL, "");

        if (account.length() == 0) {
            // Account not setup at all
            return true;
        }

        final AccountManager mgr = AccountManager.get(context);
        final Account[] accts = mgr.getAccountsByType(ACCOUNT_TYPE);
        for (final Account acct : accts) {
            if (acct.name.equals(account)) {
                // Account setup and found on system
                return false;
            }
        }

        // Account setup, but no longer present on system
        final SharedPreferences.Editor editor = prefs.edit();
        editor.putString(EditPreferences.KEY_ACCOUNT_EMAIL, null);
        editor.putString(EditPreferences.KEY_ACCOUNT_COOKIE, null);
        editor.apply();

        return true;
    }

    public static GAE getInstance(final Context context) {
        synchronized (sInstanceSync) {
            if (sInstance == null) {
                Util.log("Instantiate GAE object");
                sInstance = new GAE(context);
            }
        }
        return sInstance;
    }

    private GAE(final Context context) {
        mContext = context;
        mHandler = new Handler();
    }

    public List<String> getGoogleAccounts() {
        final ArrayList<String> result = new ArrayList<String>();
        final Account[] accounts = AccountManager.get(mContext).getAccountsByType(ACCOUNT_TYPE);
        for (final Account account : accounts) {
            result.add(account.name);
        }

        return result;
    }

    public void connect(final GaeListener listener, final String account) {
        if ((listener == null) || (account == null))
            throw new IllegalArgumentException("No null arguments allowed");

        final Runnable r = new Runnable() {
            @Override
            public void run() {
                synchronized (mListenerSync) {
                    if (mListener == null) {
                        Util.log("Connect using " + account);
                        mListener = listener;
                        mAccountName = account;
                        doConnect();
                    } else
                        mHandler.postDelayed(this, 100);
                }
            }
        };
        mHandler.post(r);
    }

    public void getPage(final GaeListener listener, final String page) {
        if ((listener == null) || (page == null))
            throw new IllegalArgumentException("No null arguments allowed");

        if (mContext.getResources().getBoolean(R.bool.gae_force_cookie_expired)) {
            final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(mContext);
            prefs.edit().putString(EditPreferences.KEY_ACCOUNT_COOKIE, "ExpiredCookie").apply();
        }

        final Runnable r = new Runnable() {
            @Override
            public void run() {
                synchronized (mListenerSync) {
                    if (mListener == null) {
                        Util.log("Getting page " + page);
                        mListener = listener;
                        new GetPageTask().execute(page);
                    } else
                        mHandler.postDelayed(this, 100);
                }
            }
        };
        mHandler.post(r);
    }

    public void postPage(final GaeListener listener, final String page, final String json) {
        if ((listener == null) || (page == null) || (json == null))
            throw new IllegalArgumentException("No null arguments allowed");

        if (mContext.getResources().getBoolean(R.bool.gae_force_cookie_expired)) {
            final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(mContext);
            prefs.edit().putString(EditPreferences.KEY_ACCOUNT_COOKIE, "ExpiredCookie").apply();
        }

        final Runnable r = new Runnable() {
            @Override
            public void run() {
                synchronized (mListenerSync) {
                    if (mListener == null) {
                        Util.log("Posting to " + page + " page: " + json);
                        mListener = listener;
                        new PostPageTask().execute(page, json);
                    } else
                        mHandler.postDelayed(this, 100);
                }
            }
        };
        mHandler.post(r);
    }

    private boolean isOnline() {
        final ConnectivityManager cm = (ConnectivityManager) mContext
                .getSystemService(Context.CONNECTIVITY_SERVICE);
        final NetworkInfo netInfo = cm.getActiveNetworkInfo();

        return (netInfo != null) && (netInfo.isConnected());
    }

    private void doConnect() {
        final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(mContext);
        final SharedPreferences.Editor editor = prefs.edit();
        editor.putString(EditPreferences.KEY_ACCOUNT_EMAIL, null);
        editor.putString(EditPreferences.KEY_ACCOUNT_COOKIE, null);
        editor.apply();

        // HACK: The GAE class should not need to know what to clear on a user
        // change. Ideally, the components would register for this event and
        // handle this themselves. However, the fragments are not guaranteed to
        // be instantiated, and therefore not registered. Handle here for now.
        mContext.getSharedPreferences(Questions.ACACHE, Activity.MODE_PRIVATE).edit().clear().apply();
        mContext.deleteFile(Standings.FILENAME);

        reconnect();
    }

    @SuppressLint("NewApi")
    @SuppressWarnings("deprecation")
    private boolean reconnect() {
        final AccountManager mgr = AccountManager.get(mContext);
        final Account[] accts = mgr.getAccountsByType(ACCOUNT_TYPE);
        for (final Account acct : accts) {
            if (acct.name.equals(mAccountName)) {
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
                    mgr.getAuthToken(acct, "ah", null, false, new AuthTokenCallback(), null);
                } else {
                    mgr.getAuthToken(acct, "ah", false, new AuthTokenCallback(), null);
                }
                return true;
            }
        }
        Util.log("Account " + mAccountName + " not found!");
        return false;
    }

    private class AuthTokenCallback implements AccountManagerCallback<Bundle> {
        @Override
        public void run(final AccountManagerFuture<Bundle> future) {
            try {
                final Bundle result = future.getResult();

                final Intent launch = (Intent) result.get(AccountManager.KEY_INTENT);
                if (launch != null) {
                    Util.log("Need to launch activity before getting authToken");
                    // How can we get the result of the activity if it is a new task!?
                    launch.setFlags(launch.getFlags() & ~Intent.FLAG_ACTIVITY_NEW_TASK);
                    cbLaunchIntent(launch);
                    return;
                }

                final String authToken = result.getString(AccountManager.KEY_AUTHTOKEN);
                if (mNeedInvalidate) {
                    Util.log("Invalidating token and starting over");
                    mNeedInvalidate = false;

                    final AccountManager mgr = AccountManager.get(mContext);
                    mgr.invalidateAuthToken(ACCOUNT_TYPE, authToken);
                    reconnect();
                } else {
                    Util.log("authToken=" + authToken);
                    mNeedInvalidate = true;

                    // Phase 2: get authCookie from PTW server
                    new GetCookieTask().execute(authToken);
                }
            } catch (final IOException e) {
                Util.log("Get auth token failed with IOException: online=" + isOnline());

                if (isOnline() && (mRemainingRetries > 0)) {
                    mRemainingRetries--;
                    reconnect();
                } else
                    cbFailedConnect();
            } catch (final Exception e) {
                Util.log("Get auth token failed with exception " + e);
                cbFailedConnect();
            }
        }
    }

    private class GetCookieTask extends AsyncTask<String, Integer, Boolean> {
        @Override
        protected Boolean doInBackground(final String... tokens) {
            String authCookie = null;

            try {
                final DefaultHttpClient client = new DefaultHttpClient();
                final URI uri = new URI(PROD_URL + "/_ah/login?continue=" + URLEncoder.encode(PROD_URL, "UTF-8")
                        + "&auth=" + tokens[0]);
                final HttpGet method = new HttpGet(uri);
                final HttpParams getParams = new BasicHttpParams();
                HttpClientParams.setRedirecting(getParams, false);
                HttpConnectionParams.setConnectionTimeout(getParams, TIMEOUT_CONNECTION);
                HttpConnectionParams.setSoTimeout(getParams, TIMEOUT_SOCKET);
                method.setParams(getParams);

                final HttpResponse res = client.execute(method);
                final Header[] headers = res.getHeaders("Set-Cookie");
                final int statusCode = res.getStatusLine().getStatusCode();
                if (statusCode != 302 || headers.length == 0) {
                    Util.log("Get auth cookie failed: statusCode=" + statusCode);
                    return false;
                }

                for (final Cookie cookie : client.getCookieStore().getCookies()) {
                    if (AUTH_COOKIE_NAME.equals(cookie.getName())) {
                        authCookie = AUTH_COOKIE_NAME + "=" + cookie.getValue();
                        Util.log(authCookie);
                    }
                }
            } catch (final Exception e) {
                Util.log("Get auth cookie failed with exception " + e);
                return false;
            }

            final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(mContext);
            final SharedPreferences.Editor editor = prefs.edit();
            editor.putString(EditPreferences.KEY_ACCOUNT_EMAIL, mAccountName);
            editor.putString(EditPreferences.KEY_ACCOUNT_COOKIE, authCookie);
            editor.apply();

            return true;
        }

        @Override
        protected void onPostExecute(final Boolean success) {
            if (success) {
                if (mJson != null)
                    new PostPageTask().execute(mGetPage, mJson);
                else if (mGetPage != null)
                    new GetPageTask().execute(mGetPage);
                else
                    cbConnectSuccess(null);
            } else
                cbFailedConnect();
        }
    }

    private class GetPageTask extends AsyncTask<String, Integer, Boolean> {
        final StringBuilder mBuilder = new StringBuilder();

        @Override
        protected Boolean doInBackground(final String... pages) {
            mGetPage = null;
            final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(mContext);
            final DefaultHttpClient client = new DefaultHttpClient();
            final HttpGet method = new HttpGet(PROD_URL + "/" + pages[0]);

            final HttpParams getParams = new BasicHttpParams();
            HttpClientParams.setRedirecting(getParams, false);
            HttpConnectionParams.setConnectionTimeout(getParams, TIMEOUT_CONNECTION);
            HttpConnectionParams.setSoTimeout(getParams, TIMEOUT_SOCKET);
            method.setParams(getParams);
            method.setHeader("Cookie", prefs.getString(EditPreferences.KEY_ACCOUNT_COOKIE, null));

            try {
                final HttpResponse resp = client.execute(method);

                switch (resp.getStatusLine().getStatusCode()) {
                case HttpStatus.SC_OK:
                    final BufferedReader reader = new BufferedReader(
                            new InputStreamReader(resp.getEntity().getContent()));
                    String line;
                    while ((line = reader.readLine()) != null) {
                        mBuilder.append(line).append('\n');
                    }
                    break;
                case HttpStatus.SC_MOVED_TEMPORARILY:
                    if (mAccountName != null) {
                        Util.log("Get page failed (status 302)");
                        return false;
                    }
                    Util.log("Cookie expired? Attempting reconnect");
                    mGetPage = pages[0];
                    mAccountName = prefs.getString(EditPreferences.KEY_ACCOUNT_EMAIL, null);
                    return reconnect();
                default:
                    Util.log("Get page failed (invalid status code)");
                    return false;
                }
            } catch (final Exception e) {
                Util.log("Get page failed with exception " + e);
                return false;
            }

            return true;
        }

        @Override
        protected void onPostExecute(final Boolean success) {
            if (success) {
                if (mGetPage == null)
                    cbGet(mBuilder.toString());
            } else
                cbFailedConnect();
        }
    }

    private class PostPageTask extends AsyncTask<String, Integer, Boolean> {
        String mJsonReturned;

        @Override
        protected Boolean doInBackground(final String... args) {
            mJson = null;
            final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(mContext);
            final DefaultHttpClient client = new DefaultHttpClient();
            final HttpPost method = new HttpPost(PROD_URL + "/" + args[0]);

            final HttpParams postParams = new BasicHttpParams();
            HttpClientParams.setRedirecting(postParams, false);
            HttpConnectionParams.setConnectionTimeout(postParams, TIMEOUT_CONNECTION);
            HttpConnectionParams.setSoTimeout(postParams, TIMEOUT_SOCKET);
            method.setParams(postParams);
            method.setHeader("Cookie", prefs.getString(EditPreferences.KEY_ACCOUNT_COOKIE, null));

            try {
                final StringEntity se = new StringEntity(args[1]);
                se.setContentType(new BasicHeader(HTTP.CONTENT_TYPE, "application/json"));
                method.setEntity(se);

                final HttpResponse resp = client.execute(method);

                switch (resp.getStatusLine().getStatusCode()) {
                case HttpStatus.SC_OK:
                    final BufferedReader reader = new BufferedReader(
                            new InputStreamReader(resp.getEntity().getContent()));
                    mJsonReturned = reader.readLine();
                    break;
                case HttpStatus.SC_MOVED_TEMPORARILY:
                    if (mAccountName != null) {
                        Util.log("Post page failed (status 302)");
                        return false;
                    }
                    Util.log("Cookie expired? Attempting reconnect");
                    mGetPage = args[0];
                    mJson = args[1];
                    mAccountName = prefs.getString(EditPreferences.KEY_ACCOUNT_EMAIL, null);
                    return reconnect();
                default:
                    Util.log("Post page failed (invalid status code)");
                    return false;
                }
            } catch (final Exception e) {
                Util.log("Post page failed with exception " + e);
                return false;
            }

            return true;
        }

        @Override
        protected void onPostExecute(final Boolean success) {
            if (success) {
                if (mJson == null)
                    cbConnectSuccess(mJsonReturned);
            } else
                cbFailedConnect();
        }
    }

    private void cbFailedConnect() {
        mListener.onFailedConnect(mContext);
        synchronized (mListenerSync) {
            mGetPage = mJson = mAccountName = null;
            mListener = null;
        }
    }

    private void cbLaunchIntent(final Intent launch) {
        mListener.onLaunchIntent(launch);
        synchronized (mListenerSync) {
            mGetPage = mJson = mAccountName = null;
            mListener = null;
        }
    }

    private void cbConnectSuccess(final String json) {
        mListener.onConnectSuccess(mContext, json);
        synchronized (mListenerSync) {
            mGetPage = mJson = mAccountName = null;
            mListener = null;
        }
    }

    private void cbGet(final String json) {
        mListener.onGet(mContext, json);
        synchronized (mListenerSync) {
            mGetPage = mJson = mAccountName = null;
            mListener = null;
        }
    }
}