package com.jobmineplus.mobile.activities.jbmnpls; import java.io.IOException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.Locale; import javax.net.ssl.SSLException; import org.apache.http.NameValuePair; import org.apache.http.message.BasicNameValuePair; import android.app.Activity; import android.app.AlertDialog.Builder; import android.content.DialogInterface; import android.content.Intent; import android.os.Bundle; import android.support.v4.app.NavUtils; import android.util.Pair; import android.view.MenuItem; import com.jobmineplus.mobile.R; import com.jobmineplus.mobile.activities.HomeActivity; import com.jobmineplus.mobile.activities.LoggedInActivityBase; import com.jobmineplus.mobile.database.jobs.JobDataSource; import com.jobmineplus.mobile.database.pages.PageDataSource; import com.jobmineplus.mobile.database.users.UserDataSource; import com.jobmineplus.mobile.debug.DebugHomeActivity; import com.jobmineplus.mobile.exceptions.HiddenColumnsException; import com.jobmineplus.mobile.exceptions.InfiniteLoopException; import com.jobmineplus.mobile.exceptions.JbmnplsLoggedOutException; import com.jobmineplus.mobile.exceptions.JbmnplsParsingException; import com.jobmineplus.mobile.widgets.DatabaseTask; import com.jobmineplus.mobile.widgets.DatabaseTask.Action; import com.jobmineplus.mobile.widgets.DatabaseTask.IDatabaseTask; import com.jobmineplus.mobile.widgets.Job; import com.jobmineplus.mobile.widgets.ProgressDialogAsyncTaskBase; import com.jobmineplus.mobile.widgets.StopWatch; public abstract class JbmnplsActivityBase extends LoggedInActivityBase implements IDatabaseTask<Long>, DialogInterface.OnClickListener { // ================= // Declarations // ================= protected static final SimpleDateFormat DISPLAY_DATE_FORMAT = new SimpleDateFormat("MMM d, yyyy", Locale.getDefault()); protected final static String EXTRA_JOB_ID = "jobId"; private String dataUrl = null; // Use JbmnPlsHttpService.GET_LINKS.<url> protected ArrayList<Job> allJobs; protected GetHtmlTask task = null; protected JobDataSource jobDataSource; protected PageDataSource pageDataSource; protected long timestamp; private String pageName; private Boolean backBtnDisabled = false; private DatabaseTask<Long> databaseTask; private Builder confirm; private Bundle savedInstance; // ==================== // Abstract Methods // ==================== public abstract String getPageName(); public abstract String getUrl(); /** * Here you are given the document of the dataUrl page specified in setUp(). * Also render the layout with the data here. * * @param doc */ protected abstract void parseWebpage(String html); /** * Calling this when the parseWebpage is complete. This is used when you * need to update any visual element that you cannot do in parseWebpage * * @param doc */ protected abstract void onRequestComplete(boolean pulledData); protected abstract long doOffine(); // ==================== // Override Methods // ==================== @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); savedInstance = savedInstanceState; allJobs = new ArrayList<Job>(); jobDataSource = new JobDataSource(this); pageDataSource = new PageDataSource(this); jobDataSource.open(); pageDataSource.open(); confirm = new Builder(this); confirm.setPositiveButton("Yes", this).setNegativeButton("No", this) .setMessage(getString(R.string.go_offline_message)); dataUrl = getUrl(); pageName = getPageName(); getSupportActionBar().setTitle(pageName.substring(pageName.lastIndexOf(".") + 1)); } @Override public void onAttachedToWindow() { super.onAttachedToWindow(); // Coming from home we want to request data, returning after clearing ram, do offline if (savedInstance == null) { requestData(); } else { doExecuteGetTask(); } } @Override protected void onDestroy() { jobDataSource.close(); pageDataSource.close(); super.onDestroy(); } @Override public void onBackPressed() { if (!backBtnDisabled) { super.onBackPressed(); } } // ======================== // Confirm dialogue click // ======================== @Override public void onClick(DialogInterface dialog, int which) { switch (which){ case DialogInterface.BUTTON_POSITIVE: setOnlineMode(false); doExecuteGetTask(); break; case DialogInterface.BUTTON_NEGATIVE: goToHomeActivity(); break; } } // ======================== // Login/Logout Methods // ======================== protected boolean verifyLogin() { // TODO put this in a lower case so that Loginactivity can use it as well if (getLastUser() == null) { return false; } return client.verifyLogin(); } protected Pair<String, String> getLastUser() { String username = client.getUsername(); String password = client.getPassword(); if (username == null || password == null) { UserDataSource userDataSource = new UserDataSource(this); userDataSource.open(); Pair<String, String> credentials = userDataSource.getLastUser(); if (credentials != null) { client.setLoginCredentials(credentials.first, credentials.second); } userDataSource.close(); return credentials; } else { return new Pair<String, String>(username, password); } } @Override public void finish() { } private void internalFinish() { super.finish(); } // ====================== // Activity Movements // ====================== protected void goToHomeActivity(String reasonMsg) { startActivityWithMessage(HomeActivity.class, reasonMsg); } protected void goToHomeActivity() { if (isDebug()) { startActivity(DebugHomeActivity.class); } else { startActivity(HomeActivity.class); } } protected void goToDescription(int jobId) { BasicNameValuePair pass = new BasicNameValuePair(EXTRA_JOB_ID, Integer.toString(jobId)); startActivity(Description.class, pass); } protected void startActivityWithMessage(Class<?> cls, String reasonMsg) { Intent in = new Intent(this, cls); in.putExtra(HomeActivity.INTENT_REASON, reasonMsg); startActivity(in); finish(); } protected void startActivity(Class<?> goToClass) { NameValuePair[] empty = null; startActivity(goToClass, empty); } protected void startActivity(Class<?> goToClass, NameValuePair... args) { Intent in = new Intent(this, goToClass); if (args != null) { for (NameValuePair arg : args) { in.putExtra(arg.getName(), arg.getValue()); } } startActivity(in); } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { // Respond to the action bar's Up/Home button case android.R.id.home: NavUtils.navigateUpFromSameTask(this); return true; } return super.onOptionsItemSelected(item); } // ====================== // Database Task Members // ====================== @Override public Long doPutTask() { backBtnDisabled = true; // Shallow copy of all jobs so that it avoids concurrency issues ArrayList<Job> copyAllJobs = new ArrayList<Job>(allJobs); jobDataSource.addJobs(copyAllJobs); if (pageName != null) { pageDataSource.addPage(client.getUsername(), pageName, copyAllJobs, timestamp); } backBtnDisabled = false; return null; } @Override public Long doGetTask() { // Handle the moments when username is null, eg. coming back from clearing ram if (getLastUser() == null) { return (long) -1; } return doOffine(); } @Override public void finishedTask(Long result, DatabaseTask.Action action) { if (action == Action.GET) { if (result > 0) { getSupportActionBar().setSubtitle(formatDateFromNow(result, "Last accessed")); } else if (result == 0) { getSupportActionBar().setSubtitle(getString(R.string.never_accessed_before)); } else { throw new IllegalStateException("It is impossible to go to this screen without logging in."); } onRequestComplete(false); } } private void doExecuteGetTask() { getSupportActionBar().setSubtitle(" "); databaseTask = new DatabaseTask<Long>(this); databaseTask.executeGet(); } // ================= // Miscellaneous // ================= protected String formatDateFromNow(Date then) { return formatDateFromNow(then.getTime()); } protected String formatDateFromNow(long then) { return formatDateFromNow(then, null); } protected String formatDateFromNow(long then, String prefix) { Date now = new Date(); prefix = (prefix == null) ? "" : prefix.trim(); StringBuilder sb = new StringBuilder(prefix).append(" "); float diffSecs = Math.round((now.getTime() - then) / 1000); if (diffSecs > (60*60*24)) { // Return the parsed Date return sb.append("on ") .append(DISPLAY_DATE_FORMAT.format(then)).toString(); } else { if (diffSecs < 60) { // Seconds sb.append((int)diffSecs + " second"); } else if (diffSecs < (60*60)) { // Minutes diffSecs = Math.round(diffSecs/60); sb.append((int)diffSecs + " minute"); } else { // Hours diffSecs = Math.round(diffSecs/(60*60)); sb.append((int)diffSecs + " hour"); } if (diffSecs > 1) { sb.append("s ago"); } else { sb.append(" ago"); } return sb.toString(); } } protected void jobsToDatabase() { if (isReallyOnline()) { databaseTask = new DatabaseTask<Long>(this); databaseTask.executePut(); } } protected void addJob(Job job) { allJobs.add(job); } protected boolean isLoading() { return task != null && task.isRunning(); } // ==================================== // Data Request Classes and Methods // ==================================== protected void requestData() throws RuntimeException { if (isOnline()) { if (!isReallyOnline()) { confirm.show(); } else { task = new GetHtmlTask(this, getString(R.string.login_message)); task.execute(dataUrl); } } else { doExecuteGetTask(); } } /** * You can override this function if you need to fetch something else * besides the default url. Return null if it failed and this class will * throw a dialog saying it failed otherwise return the html * * @param url * @return null if failed or String that is the html * @throws IOException */ protected String onRequestData(String[] args) throws JbmnplsLoggedOutException, IOException { String url = args[0]; return client.getJobmineHtml(url); } private class GetHtmlTask extends ProgressDialogAsyncTaskBase<String, String, Integer> { static final int NO_PROBLEM = 0; static final int FORCED_LOGGEDOUT = 1; static final int GO_HOME_NO_REASON = 2; static final int HIDDEN_COLUMNS_ERROR = 3; static final int PARSING_ERROR = 4; static final int NETWORK_ERROR = 5; static final int INFINITE_LOOP_ERROR = 6; private final StopWatch sw = new StopWatch(); public GetHtmlTask(Activity activity, String dialogueMessage) { super(activity, dialogueMessage, isOnline()); } @Override protected void onProgressUpdate(String... values) { super.onProgressUpdate(values); setMessage(values[0]); } @Override protected Integer doInBackground(String... params) { JbmnplsActivityBase activity = (JbmnplsActivityBase) getActivity(); if (!verifyLogin()) { return FORCED_LOGGEDOUT; } publishProgress(getString(R.string.wait_post_message)); sw.start(); String html = "empty"; try { backBtnDisabled = true; html = activity.onRequestData(params); timestamp = System.currentTimeMillis(); if (html == null) { backBtnDisabled = false; return NETWORK_ERROR; } activity.parseWebpage(html); return NO_PROBLEM; } catch (InfiniteLoopException e) { e.printStackTrace(); return INFINITE_LOOP_ERROR; } catch (HiddenColumnsException e) { e.printStackTrace(); return HIDDEN_COLUMNS_ERROR; } catch (JbmnplsParsingException e) { e.printStackTrace(); return PARSING_ERROR; } catch (JbmnplsLoggedOutException e) { e.printStackTrace(); return FORCED_LOGGEDOUT; } catch (SSLException e) { // Ignore the SSL Error which comes from user losing network signal on get/post e.printStackTrace(); return NETWORK_ERROR; } catch (IOException e) { e.printStackTrace(); return NETWORK_ERROR; } } @Override public void onCancel(DialogInterface dialog) { super.onCancel(dialog); client.abort(); finish(); } @Override protected void onPostExecute(Integer reasonForFailure) { super.onPostExecute(reasonForFailure); String renderMsg = sw.elapsed() + " ms to render"; log(renderMsg); if (reasonForFailure == NO_PROBLEM) { onRequestComplete(true); } else { switch (reasonForFailure) { case INFINITE_LOOP_ERROR: goToHomeActivity(getString(R.string.infinite_loop_error_message)); break; case FORCED_LOGGEDOUT: confirm.show(); break; case PARSING_ERROR: goToHomeActivity(getString(R.string.parsing_error_message)); break; case HIDDEN_COLUMNS_ERROR: goToHomeActivity(getString(R.string.hidden_column_message)); break; case NETWORK_ERROR: goToHomeActivity(getString(R.string.network_error)); break; case GO_HOME_NO_REASON: goToHomeActivity(""); break; } } backBtnDisabled = false; } } }