package com.jobmineplus.mobile.services; import java.io.IOException; import java.util.ArrayList; import java.util.Calendar; import java.util.HashMap; import javax.net.ssl.SSLException; import android.app.AlarmManager; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; import android.app.Service; 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.Bundle; import android.os.IBinder; import android.preference.PreferenceManager; import com.jobmineplus.mobile.R; import com.jobmineplus.mobile.activities.SimpleActivityBase; import com.jobmineplus.mobile.activities.jbmnpls.Applications; import com.jobmineplus.mobile.activities.jbmnpls.Interviews; import com.jobmineplus.mobile.database.jobs.JobDataSource; import com.jobmineplus.mobile.database.pages.PageDataSource; import com.jobmineplus.mobile.database.pages.PageMapResult; import com.jobmineplus.mobile.exceptions.JbmnplsException; import com.jobmineplus.mobile.exceptions.JbmnplsLoggedOutException; import com.jobmineplus.mobile.exceptions.JbmnplsParsingException; import com.jobmineplus.mobile.widgets.JbmnplsHttpClient; import com.jobmineplus.mobile.widgets.Job; import com.jobmineplus.mobile.widgets.table.TableParser; import com.jobmineplus.mobile.widgets.table.TableParserOutline; public class InterviewsNotifierService extends Service { private final PageDataSource pageSource = new PageDataSource(this); private final JobDataSource jobSource = new JobDataSource(this); private static final int CHECK_APPS_MAX_COUNT = 10; private JbmnplsHttpClient client; NotificationManager mNotificationManager; // Nofication values private final int INTERVIEW_NOTIFICATION_ID = 1; private Notification notification; // Time constants public final int CRAWL_APPLICATIONS_TIMEOUT = 3 * 60 * 60; // 3 hours public final int NO_DATA_RESCHEDULE_TIME = 5 * 60 * 60; // 5 hours private int originalTimeout; @Override public void onCreate() { super.onCreate(); mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); client = new JbmnplsHttpClient(); } @Override public int onStartCommand(Intent intent, int flags, int startId) { GetInterviewsTask task = new GetInterviewsTask(this); if (intent == null) { return START_STICKY; } originalTimeout = intent.getIntExtra(InterviewsAlarm.BUNDLE_TIMEOUT, 0); // Handle logging in with username and password String username = intent.getStringExtra(InterviewsAlarm.BUNDLE_USERNAME); String password = intent.getStringExtra(InterviewsAlarm.BUNDLE_PASSWORD); if (username == null || password == null) { return START_STICKY; } // If the username or password has changed, we will make a new client String oldUsername = client.getUsername(); String oldPassword = client.getPassword(); if (!username.equals(oldUsername) || !password.equals(oldPassword)) { client = new JbmnplsHttpClient(username, password); } if (!client.verifyLogin()) { return START_STICKY; } task.execute(originalTimeout); return super.onStartCommand(intent, flags, startId); } @Override public IBinder onBind(Intent intent) { return null; } private long validateScheduleTime(long timestamp) { // This is the next scheduled time Calendar nextTime = Calendar.getInstance(); nextTime.setTimeInMillis(timestamp); // Only apply next day if the time is during 12am->9am (offline times) int hour = nextTime.get(Calendar.HOUR_OF_DAY); if (hour < 9) { // Return tomorrow at 9am for the next schedule Calendar nextDay = Calendar.getInstance(); nextDay.add(Calendar.DAY_OF_MONTH, 1); nextDay.set(Calendar.HOUR_OF_DAY, 9); nextDay.set(Calendar.MINUTE, 0); return nextDay.getTimeInMillis(); } return timestamp; } private void scheduleNextAlarm(int timeoutSeconds) { // Bundle the next time inside the intent long now = System.currentTimeMillis(); long triggerTime = validateScheduleTime(now + timeoutSeconds * 1000); // Pass back the original timeout Bundle bundle = new Bundle(); Intent in = new Intent(this, InterviewsAlarm.class); bundle.putString(InterviewsAlarm.BUNDLE_USERNAME, client.getUsername()); bundle.putString(InterviewsAlarm.BUNDLE_PASSWORD, client.getPassword()); in.putExtra(InterviewsAlarm.BUNDLE_NAME, bundle); // Start the next alarm PendingIntent pi = PendingIntent.getBroadcast(this, 0, in, PendingIntent.FLAG_ONE_SHOT); AlarmManager am = (AlarmManager) getSystemService(ALARM_SERVICE); am.set(AlarmManager.RTC_WAKEUP, triggerTime, pi); } private void showNotification(String title, String content) { if (notification == null) { notification = new Notification(R.drawable.ic_launcher, getString(R.string.interviews_ticket_text), System.currentTimeMillis()); } else { notification.when = System.currentTimeMillis(); } Intent resultIntent = new Intent(this, Interviews.class); PendingIntent pin = PendingIntent.getActivity(this, 0, resultIntent, 0); notification.setLatestEventInfo(this, title, content, pin); mNotificationManager.notify(INTERVIEW_NOTIFICATION_ID, notification); } private class GetInterviewsTask extends AsyncTask<Integer, Void, Boolean> implements TableParser.OnTableParseListener { private final TableParser parser = new TableParser(); private ArrayList<Job> pulledJobs; private HashMap<String, ArrayList<Job>> pulledInterviewsJobs; private HashMap<String, ArrayList<Job>> pulledAppsJobs; private int nextTimeout = 0; private final Context ctx; // Results from getting interviews private final int NO_SCHEDULE = 0; private final int DO_SCHEDULE = 1; private final int DO_SCHEDULE_NO_INTERVIEW = 2; public GetInterviewsTask(Context context) { ctx = context; parser.setOnTableRowParse(this); } @Override protected Boolean doInBackground(Integer... params) { if (!SimpleActivityBase.isJobmineOnline()) { return true; } nextTimeout = params[0]; pageSource.open(); jobSource.open(); // Check connections ConnectivityManager connManager = (ConnectivityManager) getSystemService(CONNECTIVITY_SERVICE); NetworkInfo mWifi = connManager.getNetworkInfo(ConnectivityManager.TYPE_WIFI); NetworkInfo mMobile = connManager.getNetworkInfo(ConnectivityManager.TYPE_MOBILE); SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(ctx); boolean wifiOnly = preferences.getBoolean("settingsWifiCheckOnly", true); // TODO check this if (Interviews.isNetworkConnected() && (mWifi.isConnected() || mMobile.isConnected() && !wifiOnly)) { // Check the applications to then see if we need to crawl interviews int result = NO_SCHEDULE; try { result = checkApplications(); } catch (JbmnplsLoggedOutException e) { e.printStackTrace(); return false; } catch (SSLException e) { // Ignore sending this exception, it is not an error e.printStackTrace(); return false; } catch (IOException e) { e.printStackTrace(); return false; } // Parse the results if (result == NO_SCHEDULE) { return false; } else { if (result == DO_SCHEDULE) { return crawlInterviews(); } else { // Do not run interviews but will schedule return true; } } } else { // Try again later return true; } } private int checkApplications() throws JbmnplsLoggedOutException, IOException { return checkApplications(0); } private int checkApplications(int ranCount) throws JbmnplsLoggedOutException, IOException { // Avoid stackoverflow because of time calculation errors if (ranCount >= CHECK_APPS_MAX_COUNT) { return NO_SCHEDULE; } String username = client.getUsername(); PageMapResult result = pageSource.getPageDataMap(username, Applications.PAGE_NAME); // If no results, get them then if (result == null) { crawlApplications(); result = pageSource.getPageDataMap(username, Applications.PAGE_NAME); if (result == null) { throw new JbmnplsException("Cannot grab any data from Applications."); } } // Get data from result ArrayList<Integer> activeList = result.idMap.get(Applications.LISTS.ACTIVE_JOBS); ArrayList<Integer> allList = result.idMap.get(Applications.LISTS.ALL_JOBS); long now = System.currentTimeMillis(); double secDiff = (now - result.timestamp) / 1000; Boolean needToGetApps = secDiff > CRAWL_APPLICATIONS_TIMEOUT; if (needToGetApps) { crawlApplications(); return checkApplications(ranCount + 1); } // When active is empty, we do not need to get interviews if (activeList == null) { // Both lists are empty so we don't do anything or schedule anything if (allList == null) { return NO_SCHEDULE; } else { // No active apps and now we reschedule at a later time nextTimeout = NO_DATA_RESCHEDULE_TIME; return DO_SCHEDULE_NO_INTERVIEW; } } else { // Check to see if you are employed by chacking the all list for employed ArrayList<Job> jobs = jobSource.getJobsByIdList(allList); for (Job j : jobs) { if (j.getStatus() == Job.STATUS.EMPLOYED) { // We are employed, no need to check interviews at all return NO_SCHEDULE; } } return DO_SCHEDULE; } } private void crawlApplications() throws JbmnplsLoggedOutException, IOException { // Crawl the appplications pulledJobs = new ArrayList<Job>(); pulledAppsJobs = new HashMap<String, ArrayList<Job>>(); pulledAppsJobs.put(Applications.LISTS.ACTIVE_JOBS, new ArrayList<Job>()); pulledAppsJobs.put(Applications.LISTS.ALL_JOBS, new ArrayList<Job>()); // Pull data from the application webpage String html = client.getJobmineHtml(JbmnplsHttpClient.GET_LINKS.APPLICATIONS); if (html != null) { parser.execute(Applications.ACTIVE_OUTLINES, html); parser.execute(Applications.ALL_OUTLINE, html); // Put data into storage jobSource.addJobs(pulledJobs); pageSource.addPage(client.getUsername(), Applications.PAGE_NAME, pulledAppsJobs, System.currentTimeMillis()); } } private Boolean crawlInterviews() { // Get interviews data from the database HashMap<String, ArrayList<Integer>> ids = pageSource.getJobsIdMap(client.getUsername(), Interviews.PAGE_NAME); pulledJobs = new ArrayList<Job>(); pulledInterviewsJobs = new HashMap<String, ArrayList<Job>>(); pulledInterviewsJobs.put(Interviews.TABS.COMING_UP, new ArrayList<Job>()); pulledInterviewsJobs.put(Interviews.TABS.FINISHED, new ArrayList<Job>()); // Pull the interview data off the website String html; try { html = client.getJobmineHtml(JbmnplsHttpClient.GET_LINKS.INTERVIEWS); } catch (JbmnplsLoggedOutException e) { e.printStackTrace(); return false; } catch (SSLException e) { // Ignore logging this, not an error e.printStackTrace(); return false; } catch (IOException e) { e.printStackTrace(); return false; } if (html == null) { return true; } // Parse the html into jobs (except the canncelled jobs) try { parser.execute(Interviews.INTERVIEWS_OUTLINE, html); parser.execute(Interviews.GROUPS_OUTLINE, html); parser.execute(Interviews.SPECIAL_OUTLINE, html); } catch (JbmnplsParsingException e) { e.printStackTrace(); return false; } // Check to see if this is first time checking interviews on device if (ids == null) { // First time getting interviews, so we need to add it to the database jobSource.addJobs(pulledJobs); pageSource.addPage(client.getUsername(), Interviews.PAGE_NAME, pulledInterviewsJobs, System.currentTimeMillis()); } else { // Parse out which are the new interviews if (pulledJobs.isEmpty()) { return true; } // Parse the new interviews; remove all jobs that are already existing int newCount = 0; ArrayList<Integer> comingUpJobIds = ids.get(Interviews.TABS.COMING_UP); ArrayList<Integer> finishedJobIds = ids.get(Interviews.TABS.FINISHED); if (comingUpJobIds != null) { for (int i = 0; i < pulledJobs.size(); i++) { if (!comingUpJobIds.contains(pulledJobs.get(i).getId())) { newCount++; } } } if (finishedJobIds != null) { for (int i = 0; i < pulledJobs.size(); i++) { if (!finishedJobIds.contains(pulledJobs.get(i).getId())) { newCount++; } } } // No new jobs if (newCount == 0) { return true; } String message = newCount + " new interview" + (newCount==1?"":"s"); showNotification("Jobmine Plus", message); } return true; } @Override protected void onPostExecute(Boolean shouldScheduleAlarm) { pageSource.close(); jobSource.close(); if (shouldScheduleAlarm && nextTimeout != 0) { scheduleNextAlarm(nextTimeout); } super.onPostExecute(shouldScheduleAlarm); } @Override public void onRowParse(TableParserOutline outline, Object... jobData) { Job job; if (outline == Applications.ALL_OUTLINE) { job = Applications.parseRowTableOutline(outline, jobData); pulledAppsJobs.get(Applications.LISTS.ALL_JOBS).add(job); } else if (outline == Applications.ACTIVE_OUTLINES[0] || outline == Applications.ACTIVE_OUTLINES[1] || outline == Applications.ACTIVE_OUTLINES[2]) { job = Applications.parseRowTableOutline(outline, jobData); pulledAppsJobs.get(Applications.LISTS.ACTIVE_JOBS).add(job); } else { job = Interviews.parseRowTableOutline(outline, jobData); if (job.pastNow()) { pulledInterviewsJobs.get(Interviews.TABS.FINISHED).add(job); } else { pulledInterviewsJobs.get(Interviews.TABS.COMING_UP).add(job); } } pulledJobs.add(job); } } }