com.layer8apps.CalendarHandler.java Source code

Java tutorial

Introduction

Here is the source code for com.layer8apps.CalendarHandler.java

Source

/*
 * Copyright 2012, 2013 Devin Collins <agent1709@gmail.com>,
 * Bobby Ore <bob1987@gmail.com>, Casey Stark <starkca90@gmail.com>
 *
 * This file is part of MyTLC Sync.
 *
 * MyTLC Sync 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.
 *
 * MyTLC Sync 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 MyTLC Sync.  If not, see <http://www.gnu.org/licenses/>.
 */

package com.layer8apps;

import android.app.IntentService;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.os.*;
import android.preference.Preference;
import android.provider.CalendarContract;
import android.util.Log;
import org.apache.http.NameValuePair;
import org.apache.http.message.BasicNameValuePair;

import java.util.*;

/************
 *  PURPOSE: This class handles our HTTP requests in the background
 *      so as not to lock up our primary thread and make the program
 *      seemed locked up
 *  AUTHOR: Devin Collins <agent14709@gmail.com>
 *************/
public class CalendarHandler extends IntentService {

    // Public variables used throughout the application
    private String username;
    private String password;
    private int calID = -1;
    private String loginToken = null;
    private String wbat = null;
    private List<String[]> finalDays;
    private Messenger messenger;

    public CalendarHandler() {
        super("CalendarHandler");
    }

    /************
     *  PURPOSE: Used to send our updates back to the main thread
     *  ARGUMENTS: String status
     *  RETURNS: VOID
     *  AUTHOR: Devin Collins <agent14709@gmail.com>
     *************/
    private void updateStatus(String status) {
        Message msg = Message.obtain();
        Bundle data = new Bundle();
        data.putString("status", status);
        msg.setData(data);
        try {
            messenger.send(msg);
        } catch (Exception e) {
            // TODO: Error reporting?
        }
    }

    /************
     *  PURPOSE: Used to send our errors back to the main thread
     *  ARGUMENTS: String error
     *  RETURNS: VOID
     *  AUTHOR: Devin Collins <agent14709@gmail.com>
     *************/
    private void showError(String error) {
        Message msg = Message.obtain();
        Bundle data = new Bundle();
        data.putString("status", "ERROR");
        data.putString("error", error);
        msg.setData(data);
        try {
            messenger.send(msg);
        } catch (Exception e) {
            // TODO: Error reporting?
        }
    }

    private boolean isTlcActive(String data) {
        if (data.contains("/etm/time/timesheet/etmTnsMonth.jsp")) {
            return true;
        }

        return false;
    }

    /************
     *  PURPOSE: Handles the primary thread in the service
     *  ARGUMENTS: Intent intent
     *  RETURNS: VOID
     *  AUTHOR: Devin Collins <agent14709@gmail.com>
     *************/
    @Override
    protected void onHandleIntent(Intent intent) {
        // Get our stored data from the intent
        username = intent.getStringExtra("username");
        password = intent.getStringExtra("password");
        messenger = (Messenger) intent.getExtras().get("handler");
        calID = intent.getIntExtra("calendarID", -1);

        String url = getApplicationContext().getResources().getString(R.string.url);

        // Create variables to be used through the application
        List<String[]> workDays = null;
        ConnectionManager conn = ConnectionManager.newConnection();

        /************
         * Once we verify that we have a valid token, we get the actual schedule
         *************/
        updateStatus("Logging in...");
        String tempToken = conn.getData(url + "/etm/login.jsp");
        if (tempToken != null) {
            loginToken = parseToken(tempToken);
        } else {
            showError("Error connecting to MyTLC, make sure you have a valid network connection");
            return;
        }

        String postResults = null;
        // This creates our login information
        List<NameValuePair> parameters = createParams();
        if (loginToken != null) {
            // Here we send the information to the server and login
            postResults = conn.postData(url + "/etm/login.jsp", parameters);
        } else {
            showError("Error retrieving your login token");
            return;
        }

        if (postResults != null && postResults.toLowerCase().contains("etmmenu.jsp")
                && !postResults.toLowerCase().contains("<font size=\"2\">")) {
            updateStatus("Retrieving schedule...");
            postResults = conn.getData(url + "/etm/time/timesheet/etmTnsMonth.jsp");
        } else {
            String error = parseError(postResults);
            if (error != null) {
                showError(error);
            } else {
                showError("Error logging in, please verify your username and password");
            }
            return;
        }

        // If we successfully got the information, then parse out the schedule to read it properly
        String secToken = null;
        if (postResults != null) {
            updateStatus("Parsing schedule...");
            workDays = parseSchedule(postResults);
            secToken = parseSecureToken(postResults);
            wbat = parseWbat(postResults);
        } else {
            showError("Could not obtain user schedule");
            return;
        }

        if (secToken != null) {
            parameters = createSecondParams(secToken);
            postResults = conn.postData(url + "/etm/time/timesheet/etmTnsMonth.jsp", parameters);
        } else {
            String error = parseError(postResults);
            if (error != null) {
                showError(error);
            } else {
                showError("Error retrieving your security token");
            }
            return;
        }

        List<String[]> secondMonth = null;
        if (postResults != null) {
            secondMonth = parseSchedule(postResults);
        } else {
            showError("Could not obtain user schedule");
            return;
        }

        if (secondMonth != null) {
            if (workDays == null) {
                workDays = secondMonth;
            } else {
                workDays.addAll(secondMonth);
            }
            finalDays = workDays;
        } else {
            String error = parseError(postResults);
            if (error != null) {
                showError(error);
            } else {
                showError("There was an error retrieving your schedule");
            }
            return;
        }

        // Add our shifts to the calendar
        updateStatus("Adding shifts to calendar...");
        int numShifts = addDays();

        if (finalDays != null && numShifts > -1) {
            // Report back that we're successful!
            Message msg = Message.obtain();
            Bundle b = new Bundle();
            b.putString("status", "DONE");
            b.putInt("count", numShifts);
            msg.setData(b);
            try {
                messenger.send(msg);
            } catch (Exception e) {
                // Nothing
            }
        } else {
            showError("Couldn't add your shifts to your calendar");
        }
    }

    /************
     *   PURPOSE: Attempts to parse out the error message from MyTLC
     *   ARGUMENTS: String data
     *   RETURNS: String
     *   AUTHOR: Devin Collins <agent14709@gmail.com>
     *************/
    private String parseError(String data) {
        String result = null;
        try {
            if (!data.toLowerCase().contains("<font ")) {
                return null;
            }
            result = data.substring(data.toLowerCase().indexOf("<font ") + 15);
            result = result.substring(0, result.toLowerCase().indexOf("</font>"));
            result = "MyTLC Error: " + result.trim();
        } catch (Exception e) {
            return null;
        }
        return result;
    }

    /************
     *   PURPOSE: Parses out the initial string to retrieve the connection
     *       token required by the website
     *   ARGUMENTS: String token
     *   RETURNS: String
     *   AUTHOR: Devin Collins <agent14709@gmail.com>
     *************/
    private String parseToken(String token) {
        String tempToken;
        try {
            tempToken = token.substring(token.toLowerCase().indexOf("end hotkey for submit"));
            tempToken = tempToken.substring(tempToken.toLowerCase().indexOf("hidden") + 14,
                    tempToken.toLowerCase().indexOf("url_login_token") - 7);
            return tempToken;
        } catch (Exception e) {
            return null;
        }

    }

    /************
     *   PURPOSE: Parses out the a secure token used for HTTPS browsing
     *   ARGUMENTS: String token
     *   RETURNS: String
     *   AUTHOR: Devin Collins <agent14709@gmail.com>
     *************/
    private String parseSecureToken(String token) {
        String tempToken;
        if (token.toLowerCase().contains("secureToken")) {
            try {
                tempToken = token.substring(token.toLowerCase().indexOf("secureToken") + 20, token.indexOf("'/>"));
                return tempToken;
            } catch (Exception e) {
                return null;
            }
        } else if (token.toLowerCase().contains("id='wbat")) {
            try {
                token = token.substring(token.toLowerCase().indexOf("name='wbat'"));
                tempToken = token.substring(token.toLowerCase().indexOf("id='wbat") + 17, token.indexOf("'>"));
                return tempToken;
            } catch (Exception e) {
                return null;
            }
        }
        return null;
    }

    /************
     *   PURPOSE: Parses out the a wbat token used for HTTPS browsing
     *   ARGUMENTS: String token
     *   RETURNS: String
     *   AUTHOR: Devin Collins <agent14709@gmail.com>
     *************/
    private String parseWbat(String token) {
        String tempToken;
        if (token.toLowerCase().contains("id='wbat")) {
            try {
                token = token.substring(token.toLowerCase().indexOf("name='wbat'"));
                tempToken = token.substring(token.toLowerCase().indexOf("id='wbat") + 17, token.indexOf("'>"));
                return tempToken;
            } catch (Exception e) {
                return null;
            }
        }
        return null;
    }

    private List<NameValuePair> createAPIinfo() {
        List<NameValuePair> params = new LinkedList<NameValuePair>();
        try {
            //            params.add(new BasicNameValuePair("user_id", username));
            params.add(new BasicNameValuePair("device_type", "Android"));
            return params;
        } catch (Exception e) {
            return null;
        }
    }

    /************
     *   PURPOSE: Creates the list of paramters for our initial POST request
     *   ARGUMENTS: NULL
     *   RETURNS: List<NameValuePair>
     *   AUTHOR: Devin Collins <agent14709@gmail.com>
     *************/
    private List<NameValuePair> createParams() {
        List<NameValuePair> params = new LinkedList<NameValuePair>();
        try {
            params.add(new BasicNameValuePair("pageAction", "login"));
            params.add(new BasicNameValuePair("url_login_token", loginToken));
            params.add(new BasicNameValuePair("login", username));
            params.add(new BasicNameValuePair("password", password));
            params.add(new BasicNameValuePair("client", "DEFAULT"));
            params.add(new BasicNameValuePair("localeSelected", "false"));
            params.add(new BasicNameValuePair("STATUS_MESSAGE_HIDDEN", ""));
            params.add(new BasicNameValuePair("wbXpos", "0"));
            params.add(new BasicNameValuePair("wbYpos", "0"));
            return params;
        } catch (Exception e) {
            return null;
        }
    }

    /************
     *   PURPOSE: Creates the list of paramters for our second POST request
     *   ARGUMENTS: String
     *   RETURNS: List<NameValuePair>
     *   AUTHOR: Devin Collins <agent14709@gmail.com>
     *************/
    private List<NameValuePair> createSecondParams(String secToken) {
        List<NameValuePair> params = new LinkedList<NameValuePair>();
        try {
            Calendar c = Calendar.getInstance();
            String month = String.valueOf(c.get(Calendar.MONTH) + 2);
            String year = String.valueOf(c.get(Calendar.YEAR));
            if (month.equals("13")) {
                month = "01";
                year = String.valueOf(Integer.parseInt(year) + 1);
            } else if (Integer.parseInt(month) < 10) {
                month = "0" + month;
            }
            params.add(new BasicNameValuePair("pageAction", ""));
            params.add(new BasicNameValuePair("NEW_MONTH_YEAR", month + "/" + year));
            params.add(new BasicNameValuePair("secureToken", secToken));
            params.add(new BasicNameValuePair("wbat", wbat));
            params.add(new BasicNameValuePair("selectedTocID", "11"));
            params.add(new BasicNameValuePair("parentID", "10"));
            params.add(new BasicNameValuePair("homePageButtonWasSelected", "false"));
            params.add(new BasicNameValuePair("bid1_action", ""));
            params.add(new BasicNameValuePair("bid1_current_row", "0"));
            params.add(new BasicNameValuePair("STATUS_MESSAGE_HIDDEN", ""));
            params.add(new BasicNameValuePair("wbXpos", "0"));
            params.add(new BasicNameValuePair("wbYpos", "0"));
            return params;
        } catch (Exception e) {
            return null;
        }
    }

    /************
     *  PURPOSE: This parses out the users schedule and returns it
     *      in a redable format
     *  ARGUMENTS: List<String[]>
     *  RETURNS: String
     *  AUTHOR: Devin Collins <agent14709@gmail.com>
     *************/
    private List<String[]> parseSchedule(String data) {
        List<String[]> workingDays = new ArrayList<String[]>();
        try {
            String tempSchedule = data.substring(data.toLowerCase().indexOf("calweekdayheader"));
            tempSchedule = tempSchedule.substring(0,
                    tempSchedule.toLowerCase().indexOf("document.forms[0].new_month_year"));
            String[] schedules = tempSchedule.split("</tr>");
            for (int x = 0; x < schedules.length - 1; x++) {
                if (!schedules[x].toLowerCase().contains("off")) {
                    if (schedules[x].toLowerCase().contains("calendarcellregularcurrent")
                            || schedules[x].toLowerCase().contains("calendarcellregularfuture")) {
                        String date;
                        if (schedules[x].toLowerCase().contains("calendarcellregularcurrent")) {
                            date = schedules[x].substring(
                                    schedules[x].toLowerCase().indexOf("calendardatecurrent") + 23,
                                    schedules[x].toLowerCase().indexOf("</span>"));
                        } else {
                            date = schedules[x].substring(
                                    schedules[x].toLowerCase().indexOf("calendardatenormal") + 22,
                                    schedules[x].toLowerCase().indexOf("</span>"));
                        }

                        schedules[x] = schedules[x]
                                .substring(schedules[x].toLowerCase().indexOf("calendartextshiftname") + 26);

                        String shifts[] = schedules[x].split("<br>");
                        for (int i = 0; i < shifts.length - 1; i++) {

                            if (shifts[i].toLowerCase().contains("am") && !shifts[i].toLowerCase().contains("<td>")
                                    || shifts[i].toLowerCase().contains("pm")
                                            && !shifts[i].toLowerCase().contains("<td>")) {
                                String dept = "";
                                if (i != shifts.length - 1) {
                                    dept = (shifts[i + 1].toLowerCase().startsWith("l-")) ? shifts[i + 1] : "";
                                }
                                if (!dept.toLowerCase().startsWith("sick-u")) {
                                    workingDays.add(new String[] { date.trim(), shifts[i].trim(), dept.trim() });
                                }
                            }
                        }

                    }
                }
            }
            return workingDays;
        } catch (Exception e) {
            return null;
        }
    }

    /************
     *  PURPOSE: Adds the parsed out events to the calendar
     *  ARGUMENTS: NULL
     *  RETURNS: VOID
     *  AUTHOR: Devin Collins <agent14709@gmail.com>, Bobby Ore <bob1987@gmail.com>
     *************/
    private int addDays() {
        ArrayList<Shift> shifts = buildShifts();

        try {
            //            deleteOldEvents();
            Preferences pf = new Preferences(this);
            // Get our stored notification time
            int notification = pf.getNotification();
            // Convert the stored time into minutes
            switch (notification) {
            case 0: {
                notification = 0;
                break;
            }
            case 1: {
                notification = 5;
                break;
            }
            case 2: {
                notification = 15;
                break;
            }
            case 3: {
                notification = 30;
                break;
            }
            case 4: {
                notification = 60;
                break;
            }
            case 5: {
                notification = 120;
                break;
            }
            case 6: {
                notification = 180;
                break;
            }
            }

            shifts = checkForDuplicates(shifts);

            if (shifts.size() == 0) {
                return 0;
            }

            TimeZone zone = TimeZone.getDefault();

            for (Shift shift : shifts) {

                /************
                 * ContentResolver and ContentValues are what we use to add
                 * our calendar events to the device
                 *************/
                ContentResolver cr = this.getContentResolver();
                ContentValues cv = new ContentValues();

                /************
                 * Here we create our calendar event based on the version code
                 *************/

                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
                    cv.put(CalendarContract.Events.CALENDAR_ID, calID);
                    cv.put(CalendarContract.Events.DESCRIPTION, shift.getDepartment());
                    cv.put(CalendarContract.Events.DTEND, shift.getEndDate().getTimeInMillis());
                    cv.put(CalendarContract.Events.DTSTART, shift.getStartDate().getTimeInMillis());
                    cv.put(CalendarContract.Events.EVENT_LOCATION, shift.getAddress());
                    cv.put(CalendarContract.Events.EVENT_TIMEZONE, zone.getID());
                    cv.put(CalendarContract.Events.HAS_ALARM, (notification == 0) ? 0 : 1);
                    cv.put(CalendarContract.Events.TITLE, shift.getTitle());
                } else {
                    cv.put("calendar_id", calID);
                    cv.put("description", shift.getDepartment());
                    cv.put("dtend", shift.getEndDate().getTimeInMillis());
                    cv.put("dtstart", shift.getStartDate().getTimeInMillis());
                    cv.put("eventLocation", shift.getAddress());
                    cv.put("eventTimezone", zone.getID());
                    cv.put("title", shift.getTitle());
                    cv.put("hasAlarm", (notification <= 0) ? 0 : 1);
                }

                /************
                 * Add our events to the calendar based on the Uri we get
                 *************/
                Uri uri = cr.insert(getEventsUri(), cv);

                if (uri == null) {
                    continue;
                }

                // Get the ID of the calendar event
                long eventID = Long.parseLong(uri.getLastPathSegment());

                pf.saveShift(String.valueOf(eventID));

                /************
                 * If we retrieved a Uri for the event, try to add the reminder
                 *************/
                if (notification > 0) {

                    /************
                     * Build our reminder based on version code
                     *************/
                    ContentValues reminders = new ContentValues();
                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
                        reminders.put(CalendarContract.Reminders.EVENT_ID, eventID);
                        reminders.put(CalendarContract.Reminders.METHOD, CalendarContract.Reminders.METHOD_ALERT);
                        reminders.put(CalendarContract.Reminders.MINUTES, notification);
                    } else {
                        reminders.put("event_id", eventID);
                        reminders.put("method", 1);
                        reminders.put("minutes", notification);
                    }
                    // Add the reminder to the system
                    cr.insert(getRemindersUri(), reminders);
                }
            }
        } catch (Exception e) {
            showError(
                    "Could not create calendar events on calendar, please make sure you have a calendar application on your device");
            return -1;
        }

        return shifts.size();
    }

    private ArrayList<Shift> checkForDuplicates(ArrayList<Shift> newShifts) {
        Preferences pf = new Preferences(this);
        ArrayList<String> oldShifts = pf.getSavedShifts();

        ContentResolver cr = getContentResolver();

        for (String id : oldShifts) {
            Cursor cursor = cr.query(getEventsUri(), new String[] { "dtstart", "dtend" },
                    "CALENDAR_ID = " + calID + " AND _ID = " + id, null, null);

            if (!cursor.moveToFirst()) {
                continue;
            }

            do {
                Calendar now = Calendar.getInstance();

                Calendar startTime = Calendar.getInstance();
                startTime.setTimeInMillis(cursor.getLong(0));

                Calendar endTime = Calendar.getInstance();
                endTime.setTimeInMillis(cursor.getLong(1));

                if (endTime.compareTo(now) == -1) {
                    deleteShift(id);
                    continue;
                }

                boolean shiftFound = false;

                for (Shift shift : newShifts) {
                    if (shift.getStartDate().get(Calendar.HOUR_OF_DAY) == startTime.get(Calendar.HOUR_OF_DAY)
                            && shift.getEndDate().get(Calendar.MINUTE) == shift.getEndDate().get(Calendar.MINUTE)) {
                        newShifts.remove(shift);
                        shiftFound = true;
                        break;
                    }
                }

                if (!shiftFound) {
                    cr.delete(getEventsUri(), "CALENDAR_ID = " + calID + " AND _ID = " + String.valueOf(id), null);
                }
            } while (cursor.moveToNext());
        }
        return newShifts;
    }

    private void deleteShift(String id) {
        Preferences pf = new Preferences(this);
        pf.deleteShift(id);
    }

    private ArrayList<Shift> buildShifts() {
        Preferences pf = new Preferences(this);

        String address = pf.getAddress();

        String timeAdjust = pf.getTimezone();

        String eventName = pf.getEventName();

        int time = 0;

        if (timeAdjust != null) {
            time = Integer.parseInt(timeAdjust);
        }

        ArrayList<Shift> shifts = new ArrayList<Shift>();

        for (int x = finalDays.size() - 1; x >= 0; x--) {
            String[] work = finalDays.get(x);
            Calendar beginTime = Calendar.getInstance();
            /************
             * Below we create variables for our day, month and year.  We then
             * check to see if the scheduled day is next month or next year so
             * we know to increase our variables properly
             *************/
            int workDay = Integer.parseInt(work[0].trim());
            int workMonth = beginTime.get(Calendar.MONTH);
            int workYear = beginTime.get(Calendar.YEAR);
            /************
             * If the day of the month for work is prior to todays date, then
             * we check if we're in December.  We increase the month and year
             * where necessary
             *************/
            if (workDay < beginTime.get(Calendar.DAY_OF_MONTH) && workMonth != 12) {
                workMonth += 1;
            } else if (workDay < beginTime.get(Calendar.DAY_OF_MONTH)) {
                workMonth = 1;
                workYear += 1;
            }
            /************
             * If the shift starts in the PM, add 12 hours
             * to the time
             *************/

            int workSHour = Integer.parseInt(work[1].substring(0, 2));
            if (work[1].substring(6, 8).equalsIgnoreCase("PM") && workSHour != 12) {
                workSHour += 12;
            } else if (work[1].substring(6, 8).equalsIgnoreCase("AM") && workSHour == 12) {
                workSHour = 0;
            }

            int workSMinute = Integer.parseInt(work[1].substring(3, 5));
            beginTime.set(workYear, workMonth, workDay, workSHour, workSMinute);

            beginTime.add(Calendar.HOUR, time);

            int workEHour = Integer.parseInt(work[1].substring(11, 13));
            /************
             * If the shift ends in the PM, add 12 hours
             * to the time
             *************/
            if (work[1].substring(17, 19).equalsIgnoreCase("PM") && workEHour != 12) {
                workEHour += 12;
            } else if (work[1].substring(17, 19).equalsIgnoreCase("AM") && workEHour == 12) {
                workEHour = 0;
            }

            int workEMinute = Integer.parseInt(work[1].substring(14, 16));
            Calendar endTime = Calendar.getInstance();

            if (workEHour < workSHour) {
                workDay += 1;
            }
            endTime.set(workYear, workMonth, workDay, workEHour, workEMinute);

            endTime.add(Calendar.HOUR, time);

            shifts.add(new Shift(eventName, work[2], (address == null) ? "" : address, beginTime, endTime));
        }

        return shifts;
    }

    /************
     *  PURPOSE: Gets the Uri for events based on Android version number
     *  ARGUMENTS: NULL
     *  RETURNS: Uri
     *  AUTHOR: Devin Collins <agent14709@gmail.com>
     *************/
    private Uri getEventsUri() {
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.FROYO) {
            return Uri.parse("content://calendar/events/");
        } else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
            return Uri.parse("content://com.android.calendar/events/");
        } else {
            return CalendarContract.Events.CONTENT_URI;
        }
    }

    /************
     *  PURPOSE: Gets the Uri for reminders based on Android version number
     *  ARGUMENTS: NULL
     *  RETURNS: Uri
     *  AUTHOR: Devin Collins <agent14709@gmail.com>
     *************/
    private Uri getRemindersUri() {
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.FROYO) {
            return Uri.parse("content://calendar/reminders");
        } else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
            return Uri.parse("content://com.android.calendar/reminders");
        } else {
            return CalendarContract.Reminders.CONTENT_URI;
        }
    }
}