com.nineash.hutsync.client.NetworkUtilities.java Source code

Java tutorial

Introduction

Here is the source code for com.nineash.hutsync.client.NetworkUtilities.java

Source

/*
 * Copyright (C) 2010 The Android Open Source Project
 *
 * 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.nineash.hutsync.client;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.NameValuePair;
import org.apache.http.ParseException;
import org.apache.http.auth.AuthenticationException;
import org.apache.http.client.CookieStore;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.conn.params.ConnManagerParams;
import org.apache.http.cookie.Cookie;
import org.apache.http.impl.client.BasicCookieStore;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;
import org.apache.http.util.EntityUtils;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;

import android.accounts.Account;
import android.accounts.AccountManager;
import android.content.ContentResolver;
import android.content.ContentProviderClient;
import android.content.ContentProviderOperation;
import android.content.Context;
import android.provider.BaseColumns;
import android.provider.CalendarContract;
import android.provider.CalendarContract.Calendars;
import android.provider.CalendarContract.Events;
import android.preference.PreferenceManager;
import android.database.Cursor;
import android.net.Uri;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.text.TextUtils;
import android.util.Log;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.UnsupportedEncodingException;
import java.io.Serializable;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import com.nineash.hutsync.Constants;
import com.nineash.hutsync.client.Base64Coder;
import com.nineash.hutsync.client.HutHttpClient;
import com.nineash.hutsync.client.Event;
import com.nineash.hutsync.client.SerializableCookie;

/**
 * Provides utility methods for communicating with the server.
 */
final public class NetworkUtilities {
    /** The tag used to log to adb console. */
    private static final String TAG = "NetworkUtilities";
    /** POST parameter name for the user's account name */
    public static final String PARAM_USERNAME = "username";
    /** POST parameter name for the user's password */
    public static final String PARAM_PASSWORD = "password";
    /** POST parameter name for the user's authentication token - now a cookie */
    public static final String PARAM_AUTH_TOKEN = "authtoken";
    /** POST parameter name for keeping user remembered, so we get an authtoken, aka cookie */
    public static final String PARAM_REMEMBER = "remember";
    /** POST parameter name for the client's last-known sync state */
    public static final String PARAM_SYNC_STATE = "syncstate";
    /** POST parameter name for the sending client-edited contact info */
    public static final String PARAM_CONTACTS_DATA = "contacts";
    /** Timeout (in ms) we specify for each http request */
    public static final int HTTP_REQUEST_TIMEOUT_MS = 30 * 1000;
    /** Base URL for the v2 Sample Sync Service */
    public static final String BASE_URI = "hutspace.co.uk";
    /** Base URL for the v2 Sample Sync Service */
    public static String BASE_URL;
    /** Protocol*/
    public static final String BASE_PROTOCOL = "http://";
    /** Secure Protocol*/
    public static final String SECURE_PROTOCOL = "https://";
    /** URI for authentication service */
    public static String AUTH_URI;
    /** URI for sync service */
    public static String SYNC_CONTACTS_URI;
    private static ContentResolver mContentResolver = null;

    public static String CUR_PROTOCOL;

    private static void initNetworkUtilities(Context context) {
        if (PreferenceManager.getDefaultSharedPreferences(context).getBoolean("use_ssl", true)) {
            CUR_PROTOCOL = SECURE_PROTOCOL;
        } else {
            CUR_PROTOCOL = BASE_PROTOCOL;
        }
        BASE_URL = CUR_PROTOCOL + BASE_URI;
        AUTH_URI = BASE_URL;
        SYNC_CONTACTS_URI = BASE_URL;
    }

    /**
     * Configures the httpClient to connect to the URL provided.
     */
    public static DefaultHttpClient getHttpClient(Context context) {
        initNetworkUtilities(context);
        DefaultHttpClient httpClient = new HutHttpClient(context);
        final HttpParams params = httpClient.getParams();
        HttpConnectionParams.setConnectionTimeout(params, HTTP_REQUEST_TIMEOUT_MS);
        HttpConnectionParams.setSoTimeout(params, HTTP_REQUEST_TIMEOUT_MS);
        ConnManagerParams.setTimeout(params, HTTP_REQUEST_TIMEOUT_MS);
        return httpClient;
    }

    private static long getCalendar(Account account) {
        // Find the Last.fm calendar if we've got one
        Uri calenderUri = Calendars.CONTENT_URI.buildUpon()
                .appendQueryParameter(Calendars.ACCOUNT_NAME, account.name)
                .appendQueryParameter(Calendars.ACCOUNT_TYPE, account.type).build();
        Cursor c1 = mContentResolver.query(calenderUri, new String[] { BaseColumns._ID }, null, null, null);
        if (c1.moveToNext()) {
            long ret = c1.getLong(0);
            c1.close();
            return ret;
        } else {
            ArrayList<ContentProviderOperation> operationList = new ArrayList<ContentProviderOperation>();

            ContentProviderOperation.Builder builder = ContentProviderOperation.newInsert(Calendars.CONTENT_URI
                    .buildUpon().appendQueryParameter(CalendarContract.CALLER_IS_SYNCADAPTER, "true")
                    .appendQueryParameter(Calendars.ACCOUNT_NAME, account.name)
                    .appendQueryParameter(Calendars.ACCOUNT_TYPE, account.type).build());
            builder.withValue(Calendars.ACCOUNT_NAME, account.name);
            builder.withValue(Calendars.ACCOUNT_TYPE, account.type);
            builder.withValue(Calendars.NAME, "Pizza Hut Shifts");
            builder.withValue(Calendars.CALENDAR_DISPLAY_NAME, "Pizza Hut Shifts");
            builder.withValue(Calendars.CALENDAR_COLOR, 0xD51007);
            builder.withValue(Calendars.CALENDAR_ACCESS_LEVEL, Calendars.CAL_ACCESS_READ);
            builder.withValue(Calendars.OWNER_ACCOUNT, account.name);
            builder.withValue(Calendars.SYNC_EVENTS, 1);
            operationList.add(builder.build());
            try {
                mContentResolver.applyBatch(CalendarContract.AUTHORITY, operationList);
            } catch (Exception e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
                return -1;
            }
            c1.close();
            return getCalendar(account);
        }
    }

    private static class SyncEntry {
        public Long raw_id = 0L;
    }

    /**
     * Connects to the SampleSync test server, authenticates the provided
     * username and password.
     *
     * @param username The server account username
     * @param password The server account password
     * @return String The authentication token returned by the server (or null)
     */
    public static String authenticate(String username, String password, Context context) {
        try {
            final HttpResponse resp;
            final HttpResponse init_resp;
            final ArrayList<NameValuePair> params = new ArrayList<NameValuePair>();
            DefaultHttpClient hClient = getHttpClient(context);
            final HttpPost init_post = new HttpPost(BASE_URL);
            final int first_cookies;
            final ArrayList<SerializableCookie> saveCooks = new ArrayList<SerializableCookie>();
            BasicCookieStore bcs = new BasicCookieStore();

            params.add(new BasicNameValuePair(PARAM_USERNAME, username));
            params.add(new BasicNameValuePair(PARAM_PASSWORD, password));
            params.add(new BasicNameValuePair(PARAM_REMEMBER, "yes"));

            init_resp = hClient.execute(init_post);
            String respString = EntityUtils.toString(init_resp.getEntity());

            List<Cookie> cookies = hClient.getCookieStore().getCookies();
            if (cookies.isEmpty()) {
                Log.e(TAG, "No cookies gathered first time round");
            }
            first_cookies = cookies.size();
            if (first_cookies != 2) {
                Log.e(TAG, "Should be two cookie to start off with");
            }

            Document doc = Jsoup.parse(respString);
            Elements hiddens = doc.select("div.homepage input[type=hidden]");

            for (Element hidden : hiddens) {
                params.add(new BasicNameValuePair(hidden.attr("name"), hidden.attr("value")));
            }

            final HttpEntity entity;
            try {
                entity = new UrlEncodedFormEntity(params);
            } catch (final UnsupportedEncodingException e) {
                // this should never happen.
                throw new IllegalStateException(e);
            }
            Log.i(TAG, "Authenticating to: " + AUTH_URI);
            final HttpPost post = new HttpPost(AUTH_URI);
            post.addHeader(entity.getContentType());
            post.setEntity(entity);
            resp = hClient.execute(post);
            String authToken = null;
            if (resp.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
                //set authtoken here

                cookies = hClient.getCookieStore().getCookies();
                if (cookies.isEmpty()) {
                    Log.e(TAG, "No cookies gathered");
                } else {
                    if (cookies.size() == first_cookies + 2) { //we get two new cookies when we log in
                        for (int i = 0; i < cookies.size(); i++) {
                            Cookie cur_cookie = cookies.get(i);
                            if (cur_cookie.isPersistent()) {
                                saveCooks.add(new SerializableCookie(cur_cookie));
                            }
                        }
                        authToken = toString(saveCooks);
                    }
                }

            }
            if ((authToken != null) && (authToken.length() > 0)) {
                Log.v(TAG, "Successful authentication");
                return authToken;
            } else {
                Log.e(TAG, "Error authenticating" + resp.getStatusLine());
                return null;
            }
        } catch (final IOException e) {
            Log.e(TAG, "IOException when getting authtoken", e);
            return null;
        } finally {
            Log.v(TAG, "getAuthtoken completing");
        }
    }

    private static ContentProviderOperation updateEvent(long calendar_id, Account account, Event event,
            long raw_id) {
        ContentProviderOperation.Builder builder;
        if (raw_id != -1) {
            builder = ContentProviderOperation.newUpdate(Events.CONTENT_URI.buildUpon()
                    .appendQueryParameter(CalendarContract.CALLER_IS_SYNCADAPTER, "true")
                    .appendQueryParameter(Calendars.ACCOUNT_NAME, account.name)
                    .appendQueryParameter(Calendars.ACCOUNT_TYPE, account.type).build());
            builder.withSelection(Events._ID + " = '" + raw_id + "'", null);
        } else {
            builder = ContentProviderOperation.newInsert(Events.CONTENT_URI.buildUpon()
                    .appendQueryParameter(CalendarContract.CALLER_IS_SYNCADAPTER, "true")
                    .appendQueryParameter(Calendars.ACCOUNT_NAME, account.name)
                    .appendQueryParameter(Calendars.ACCOUNT_TYPE, account.type).build());
        }
        long dtstart = event.getStartDate().getTime();
        long dtend = dtstart + (1000 * 60 * 60);
        if (event.getEndDate() != null)
            dtend = event.getEndDate().getTime();
        builder.withValue(Events.CALENDAR_ID, calendar_id);
        builder.withValue(Events.DTSTART, dtstart);
        builder.withValue(Events.DTEND, dtend);
        builder.withValue(Events.TITLE, event.getTitle());

        String location = "Pizza Hut";

        builder.withValue(Events.EVENT_LOCATION, location);

        String description = event.getDescription();

        builder.withValue(Events.DESCRIPTION, description);

        builder.withValue(Events._SYNC_ID, Long.valueOf(event.getId()));
        return builder.build();
    }

    /** Read the object from Base64 string. */
    private static Object fromString(String s) throws IOException, ClassNotFoundException {
        byte[] data = Base64Coder.decode(s);
        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(data));
        Object o = ois.readObject();
        ois.close();
        return o;
    }

    /** Write the object to a Base64 string. */
    private static String toString(Serializable o) throws IOException {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(baos);
        oos.writeObject(o);
        oos.close();
        return new String(Base64Coder.encode(baos.toByteArray()));
    }

    /**
     * Perform 2-way sync with the server-side contacts. We send a request that
     * includes all the locally-dirty contacts so that the server can process
     * those changes, and we receive (and return) a list of contacts that were
     * updated on the server-side that need to be updated locally.
     *
     * @param account The account being synced
     * @param authtoken The authtoken stored in the AccountManager for this
     *            account
     * @param serverSyncState A token returned from the server on the last sync
     * @param dirtyContacts A list of the contacts to send to the server
     * @return A list of contacts that we need to update locally
     */
    public static void syncCalendar(Context context, Account account, String authtoken, long serverSyncState)
            throws JSONException, ParseException, IOException, AuthenticationException {
        ArrayList<SerializableCookie> myCookies;
        CookieStore cookieStore = new BasicCookieStore();
        DefaultHttpClient hClient = getHttpClient(context);
        mContentResolver = context.getContentResolver();
        final String[] weeknames = { "rota_this_week", "rota_next_week" };

        long calendar_id = getCalendar(account);
        if (calendar_id == -1) {
            Log.e("CalendarSyncAdapter", "Unable to create HutSync event calendar");
            return;
        }

        try {
            myCookies = (ArrayList<SerializableCookie>) fromString(authtoken);
        } catch (final IOException e) {
            Log.e(TAG, "IOException when expanding authtoken", e);
            return;
        } catch (final ClassNotFoundException e) {
            Log.e(TAG, "ClassNotFoundException when expanding authtoken", e);
            return;
        }

        for (SerializableCookie cur_cookie : myCookies) {
            cookieStore.addCookie(cur_cookie.getCookie());
        }

        hClient.setCookieStore(cookieStore);
        Log.i(TAG, "Syncing to: " + SYNC_CONTACTS_URI);
        HttpGet httpget = new HttpGet(SYNC_CONTACTS_URI);
        final HttpResponse resp = hClient.execute(httpget);
        final String response = EntityUtils.toString(resp.getEntity());
        HashMap<Long, SyncEntry> localEvents = new HashMap<Long, SyncEntry>();
        ArrayList<Event> events = new ArrayList<Event>();
        Pattern p = Pattern.compile("background-color:(#[[a-f][A-F][0-9]]{6})");
        Pattern ps = Pattern
                .compile(".calendar-key span.(\\S+) \\{ background-color:(#[[a-f][A-F][0-9]]{6}); color:#fff; \\}");

        if (resp.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
            //check we are still logged in
            //if (resp.getStatusLine().getStatusCode() == HttpStatus.SC_UNAUTHORIZED) {
            //    Log.e(TAG, "Authentication exception in sending dirty contacts");
            //    throw new AuthenticationException();
            //}

            //if we are logged in
            Map<String, String> shift_types = new HashMap<String, String>();
            int length = weeknames.length;
            Document doc = Jsoup.parse(response);
            String full_name = doc.select("a[href*=" + account.name + "/profile]").first().text();

            AccountManager mAccountManager = AccountManager.get(context);
            Account[] the_accounts = mAccountManager.getAccountsByType(Constants.ACCOUNT_TYPE);
            boolean multiple_accounts = (the_accounts.length > 1);

            Elements the_styles = doc.select("style");
            for (Element the_style : the_styles) {
                String st_txt = the_style.html();
                Matcher ms = ps.matcher(st_txt);
                while (ms.find()) { // Find each match in turn; String can't do this.
                    String cname = ms.group(1); // Access a submatch group; String can't do this.
                    String ccol = ms.group(2);
                    String rname = doc.select("span." + cname).first().text();
                    Log.i(TAG, "LOOK: " + cname + ", " + ccol + ", " + rname);
                    shift_types.put(ccol, rname);
                }
            }

            for (int w = 0; w < weeknames.length; w++) {

                Elements the_dates = doc.select("div.homepage div.accord-content table[id=" + weeknames[w]
                        + "] tr.heading th:not(.skipStyles)");
                //for (Element hidden : the_dates) { //0 is Mon, 6 is Sun
                Element the_date = the_dates.first(); //figure out the year for the Monday.
                String str_v = the_date.text();
                String[] str_sub = str_v.split(" ");
                str_sub[1] = str_sub[1].trim();
                String[] date_split = str_sub[1].split("/");
                Calendar c = Calendar.getInstance();
                int this_month = c.get(Calendar.MONTH) + 1;
                int monday_month = Integer.parseInt(date_split[1]);
                int this_year = c.get(Calendar.YEAR);
                int monday_year = this_year;
                if (this_month > monday_month) {
                    monday_year++;
                } else if (this_month < monday_month) {
                    monday_year--;
                }

                SimpleDateFormat format = new SimpleDateFormat("dd/MM/yyyy");
                Date date = new Date();
                if (str_v != null && !str_v.isEmpty()) {
                    String this_date = str_sub[1] + "/" + monday_year; //we need to figure out the year - sometimes its next year

                    try {
                        date = format.parse(this_date);
                    } catch (Exception e) {
                        // TODO Auto-generated catch block  
                        e.printStackTrace();
                    }
                    Log.i(TAG, "Dates: " + this_date + " - " + date);
                }
                //}

                for (int i = 1; i < 8; ++i) { //1 is monday, 7 is sunday
                    Elements hiddens = doc.select("div.homepage div.accord-content table[id=" + weeknames[w]
                            + "] td:eq(" + Integer.toString(i) + "):not(.skipStyles) div.timeElem");
                    int add_days = i - 1;
                    for (Element hidden : hiddens) {
                        String str = hidden.text();
                        if (str != null && !str.isEmpty()) {
                            String style = hidden.attr("style");
                            String bg_col = "";
                            Matcher m = p.matcher(style);
                            if (m.find()) {
                                bg_col = m.group(1); // Access a submatch group; String can't do this.
                            }

                            Log.i(TAG, "Time: " + str + "(" + bg_col + ")");
                            String ev_description = ""; //Location too?
                            if (multiple_accounts)
                                ev_description += full_name + "\n\n";
                            String[] times = str.split(" - ");
                            String[] start_time = times[0].split(":");
                            String[] end_time = times[1].split(":");
                            int add_start_hours = Integer.parseInt(start_time[0]);
                            int add_start_minutes = Integer.parseInt(start_time[1]);
                            int add_finish_hours = Integer.parseInt(end_time[0]);
                            int add_finish_minutes = Integer.parseInt(end_time[1]);
                            String ev_shiftType = "";
                            if (bg_col != null && !bg_col.isEmpty()) {
                                ev_shiftType = (String) shift_types.get(bg_col);
                            } else {
                                ev_shiftType = "Other";
                            }
                            String ev_title = ev_shiftType + " Shift";

                            c.setTime(date);
                            c.add(Calendar.DATE, add_days);
                            c.add(Calendar.HOUR_OF_DAY, add_start_hours);
                            c.add(Calendar.MINUTE, add_start_minutes);
                            Date startDate = c.getTime();
                            long ev_id = startDate.getTime();

                            c.setTime(date);
                            c.add(Calendar.DATE, add_days);
                            if (add_finish_hours < add_start_hours) { //shift rolls to next day
                                c.add(Calendar.HOUR_OF_DAY, 24);
                                ev_description += "Shift finishes at " + times[1] + " on the next day\n\n";
                            } else {
                                c.add(Calendar.HOUR_OF_DAY, add_finish_hours);
                                c.add(Calendar.MINUTE, add_finish_minutes);
                            }
                            Date endDate = c.getTime();

                            Event ev = new Event(ev_id, ev_title, startDate, endDate, ev_description, ev_shiftType);
                            events.add(ev);
                            Log.i(TAG, "Event: " + ev);
                        }
                    }
                }
            }

            //next merge adjacent shifts
            SimpleDateFormat timeFormat = new SimpleDateFormat("HH:mm");
            Event prev_event = null;
            for (Iterator<Event> it = events.iterator(); it.hasNext();) {
                Event cur_event = it.next();
                if (prev_event != null) {
                    if (prev_event.getEndDate().compareTo(cur_event.getStartDate()) == 0) {
                        prev_event.setDescription(prev_event.getDescription() + "Merged consecutive shifts:\n"
                                + timeFormat.format(prev_event.getStartDate()) + " to "
                                + timeFormat.format(prev_event.getEndDate()) + " (" + prev_event.getShiftType()
                                + ")\n" + timeFormat.format(cur_event.getStartDate()) + " to "
                                + timeFormat.format(cur_event.getEndDate()) + " (" + cur_event.getShiftType()
                                + ")\n\n");
                        prev_event.setEndDate(cur_event.getEndDate()); //TODO: only merge if other + FOH/BOH, note times in new description
                        it.remove();
                    }
                }
                prev_event = cur_event;
            }

            //next, load local events
            Cursor c1 = mContentResolver.query(
                    Events.CONTENT_URI.buildUpon().appendQueryParameter(Events.ACCOUNT_NAME, account.name)
                            .appendQueryParameter(Events.ACCOUNT_TYPE, account.type).build(),
                    new String[] { Events._ID, Events._SYNC_ID }, Events.CALENDAR_ID + "=?",
                    new String[] { String.valueOf(calendar_id) }, null);
            while (c1 != null && c1.moveToNext()) {
                //if(is_full_sync) {
                //   deleteEvent(context, account, c1.getLong(0));
                //} else {
                SyncEntry entry = new SyncEntry();
                entry.raw_id = c1.getLong(0);
                localEvents.put(c1.getLong(1), entry);
                //}
            }
            c1.close();
            try {
                ArrayList<ContentProviderOperation> operationList = new ArrayList<ContentProviderOperation>();
                for (Event event : events) {

                    if (localEvents.containsKey(Long.valueOf(event.getId()))) {
                        SyncEntry entry = localEvents.get(Long.valueOf(event.getId()));
                        operationList.add(updateEvent(calendar_id, account, event, entry.raw_id));
                    } else {
                        operationList.add(updateEvent(calendar_id, account, event, -1));
                    }

                    if (operationList.size() >= 50) {
                        try {
                            mContentResolver.applyBatch(CalendarContract.AUTHORITY, operationList);
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                        operationList.clear();
                    }
                }

                if (operationList.size() > 0) {
                    try {
                        mContentResolver.applyBatch(CalendarContract.AUTHORITY, operationList);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            } catch (Exception e1) {
                // TODO Auto-generated catch block
                e1.printStackTrace();
                return;
            }

        } else {
            Log.e(TAG, "Server error in sending dirty contacts: " + resp.getStatusLine());
            throw new IOException();
        }
    }
}