Java tutorial
/* * Copyright (C) 2011 Cloudtec Pty Ltd * * 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 org.smap.smapTask.android.tasks; import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import org.smap.smapTask.android.listeners.TaskDownloaderListener; import org.smap.smapTask.android.loaders.PointEntry; import org.smap.smapTask.android.taskModel.FormLocator; import org.smap.smapTask.android.taskModel.TaskCompletionInfo; import org.smap.smapTask.android.taskModel.TaskResponse; import org.smap.smapTask.android.utilities.ManageForm; import org.smap.smapTask.android.utilities.ManageForm.ManageFormDetails; import org.smap.smapTask.android.utilities.ManageFormResponse; import org.smap.smapTask.android.utilities.TraceUtilities; import org.smap.smapTask.android.utilities.Utilities; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.HttpStatus; import org.apache.http.NameValuePair; import org.apache.http.auth.AuthScope; import org.apache.http.auth.UsernamePasswordCredentials; import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.message.BasicNameValuePair; import org.odk.collect.android.application.Collect; import org.odk.collect.android.database.Assignment; import org.odk.collect.android.database.TaskAssignment; import org.odk.collect.android.listeners.FormDownloaderListener; import org.odk.collect.android.listeners.InstanceUploaderListener; import org.odk.collect.android.logic.FormDetails; import org.odk.collect.android.logic.PropertyManager; import org.odk.collect.android.preferences.PreferencesActivity; import org.odk.collect.android.provider.InstanceProviderAPI; import org.odk.collect.android.provider.InstanceProviderAPI.InstanceColumns; import org.odk.collect.android.tasks.DownloadFormsTask; import org.odk.collect.android.tasks.InstanceUploaderTask; import org.odk.collect.android.tasks.InstanceUploaderTask.Outcome; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.JsonSyntaxException; import android.content.ContentResolver; import android.content.Context; import android.content.SharedPreferences; import android.database.Cursor; import android.os.AsyncTask; import android.preference.PreferenceManager; import android.util.Log; import org.smap.smapTask.android.loaders.TaskEntry; /** * Background task for downloading tasks * * @author Neil Penman (neilpenman@gmail.com) */ public class DownloadTasksTask extends AsyncTask<Void, String, HashMap<String, String>> { private TaskDownloaderListener mStateListener; HashMap<String, String> results = null; SharedPreferences settings = null; ArrayList<TaskEntry> tasks = new ArrayList<TaskEntry>(); HashMap<Long, TaskStatus> taskMap = new HashMap<Long, TaskStatus>(); HttpResponse getResponse = null; DefaultHttpClient client = null; Gson gson = null; TaskResponse tr = null; // Data returned from the server int statusCode; String serverUrl = null; // Current server String source = null; // Server name String taskURL = null; // Url to get tasks int count; // Record number of deletes String username = null; String password = null; /* * class used to store status of existing tasks in the database and their database id * A hash is created of the data stored in these object to uniquely identify the task */ private class TaskStatus { @SuppressWarnings("unused") public long tid; @SuppressWarnings("unused") public String status; @SuppressWarnings("unused") public boolean keep; public TaskStatus(long tid, String status) { this.tid = tid; this.status = status; keep = false; } } @Override protected HashMap<String, String> doInBackground(Void... values) { results = new HashMap<String, String>(); settings = PreferenceManager.getDefaultSharedPreferences(Collect.getInstance().getBaseContext()); source = Utilities.getSource(); serverUrl = settings.getString(PreferencesActivity.KEY_SERVER_URL, null); taskURL = serverUrl + "/surveyKPI/myassignments"; // Get the username and password username = settings.getString(PreferencesActivity.KEY_USERNAME, null); password = settings.getString(PreferencesActivity.KEY_PASSWORD, null); synchronise(); // Synchronise the phone with the server return results; } @Override protected void onPostExecute(HashMap<String, String> value) { synchronized (this) { if (mStateListener != null) { mStateListener.taskDownloadingComplete(value); } } } /* * Clean up after cancel */ @Override protected void onCancelled() { } @Override protected void onProgressUpdate(String... values) { synchronized (this) { if (mStateListener != null && values.length > 0) { mStateListener.progressUpdate(values[0]); } } } public void setDownloaderListener(TaskDownloaderListener sl, Context context) { synchronized (this) { mStateListener = sl; } } /* * Synchronise the tasks stored on the phone with those on the server */ private void synchronise() { Log.i("DownloadTasksTask", "Synchronise()"); if (source != null) { try { /* * Delete tasks which were cancelled on the server and then updated on * the phone during the last refresh */ count = Utilities.deleteTasksWithStatus(Utilities.STATUS_T_CANCELLED); if (count > 0) { results.put("Cancelled Tasks", count + " deleted"); } /* * Mark closed any surveys that were submitted last time and not deleted */ Utilities.closeTasksWithStatus(Utilities.STATUS_T_SUBMITTED); if (isCancelled()) { throw new CancelException("cancelled"); } ; // Return if the user cancels /* * Submit any completed forms */ Outcome submitOutcome = submitCompletedForms(); if (submitOutcome != null) { for (String key : submitOutcome.mResults.keySet()) { results.put(key, submitOutcome.mResults.get(key)); } } /* * Get an array of the existing tasks on the phone and create a hashmap indexed on the assignment id */ Utilities.getTasks(tasks, false); for (TaskEntry t : tasks) { TaskStatus ts = new TaskStatus(t.assId, t.taskStatus); taskMap.put(t.assId, ts); } /* * Get tasks from the server */ client = new DefaultHttpClient(); if (username != null && password != null) { client.getCredentialsProvider().setCredentials(new AuthScope(null, -1, null), new UsernamePasswordCredentials(username, password)); } // Call the service to get tasks from the server InputStream is = null; HttpGet getRequest = new HttpGet(taskURL); getResponse = client.execute(getRequest); statusCode = getResponse.getStatusLine().getStatusCode(); if (statusCode != HttpStatus.SC_OK) { Log.w(getClass().getSimpleName(), "Error:" + statusCode + " for URL " + taskURL); results.put("Get Assignments", getResponse.getStatusLine().getReasonPhrase()); throw new Exception(getResponse.getStatusLine().getReasonPhrase()); } else { HttpEntity getResponseEntity = getResponse.getEntity(); is = getResponseEntity.getContent(); } // De-serialise gson = new GsonBuilder().setDateFormat("dd/MM/yyyy hh:mm").create(); Reader isReader = new InputStreamReader(is); tr = gson.fromJson(isReader, TaskResponse.class); Log.i(getClass().getSimpleName(), "Message:" + tr.message); if (isCancelled()) { throw new CancelException("cancelled"); } ; // Return if the user cancels if (tr.settings != null) { SharedPreferences.Editor editor = settings.edit(); editor.putBoolean(PreferencesActivity.KEY_STORE_USER_TRAIL, tr.settings.ft_send_trail); editor.commit(); } /* * Synchronise forms * Get any forms the user does not currently have * Delete any forms that are no longer accessible to the user */ HashMap<FormDetails, String> outcome = synchroniseForms(tr.forms); for (FormDetails key : outcome.keySet()) { results.put(key.formName, outcome.get(key)); } if (isCancelled()) { throw new CancelException("cancelled"); } ; // Return if the user cancels /* * Apply task changes * Add new tasks * Update the status of tasks on the phone that have been cancelled on the server */ addAndUpdateEntries(); /* * Notify the server of the phone state * (1) Update on the server all tasks that have a status of "accepted", "rejected" or "submitted" or "cancelled" or "completed" * Note in the case of "cancelled" the client is merely acknowledging that it received the cancellation notice * (2) Pass the list of forms and versions that have been applied back to the server */ updateTaskStatusToServer(); if (isCancelled()) { throw new CancelException("cancelled"); } ; // Return if the user cancels /* * Delete all entries in the database that we are finished with */ count = Utilities.deleteTasksWithStatus(Utilities.STATUS_T_REJECTED); if (count > 0) { results.put("Rejected Tasks", count + " deleted"); } if (tr.settings != null && tr.settings.ft_delete_submitted) { count = Utilities.deleteTasksWithStatus(Utilities.STATUS_T_SUBMITTED); if (count > 0) { results.put("Submitted Tasks", count + " deleted"); } count = Utilities.deleteTasksWithStatus(Utilities.STATUS_T_CLOSED); if (count > 0) { results.put(" Tasks", count + " deleted"); } } } catch (JsonSyntaxException e) { Log.e(getClass().getSimpleName(), "JSON Syntax Error:" + " for URL " + taskURL); publishProgress(e.getMessage()); e.printStackTrace(); results.put("Error:", e.getMessage()); } catch (CancelException e) { Log.i(getClass().getSimpleName(), "Info: Download cancelled by user."); } catch (Exception e) { Log.e(getClass().getSimpleName(), "Error:" + " for URL " + taskURL); e.printStackTrace(); publishProgress(e.getMessage()); results.put("Error:", e.getMessage()); } } } private Outcome submitCompletedForms() { String selection = InstanceColumns.SOURCE + "=? and (" + InstanceColumns.STATUS + "=? or " + InstanceColumns.STATUS + "=?)"; String selectionArgs[] = { Utilities.getSource(), InstanceProviderAPI.STATUS_COMPLETE, InstanceProviderAPI.STATUS_SUBMISSION_FAILED }; ArrayList<Long> toUpload = new ArrayList<Long>(); Cursor c = null; try { c = Collect.getInstance().getContentResolver().query(InstanceColumns.CONTENT_URI, null, selection, selectionArgs, null); if (c != null && c.getCount() > 0) { c.move(-1); while (c.moveToNext()) { Long l = c.getLong(c.getColumnIndex(InstanceColumns._ID)); toUpload.add(Long.valueOf(l)); } } } catch (Exception e) { e.printStackTrace(); } finally { if (c != null) { c.close(); } } InstanceUploaderTask instanceUploaderTask = new InstanceUploaderTask(); publishProgress("Submitting " + toUpload.size() + " finalised surveys"); instanceUploaderTask.setUploaderListener((InstanceUploaderListener) mStateListener); Long[] toSendArray = new Long[toUpload.size()]; toUpload.toArray(toSendArray); Log.i(getClass().getSimpleName(), "Submitting " + toUpload.size() + " finalised surveys"); if (toUpload.size() > 0) { return instanceUploaderTask.doInBackground(toSendArray); // Already running a background task so call direct } else { return null; } } /* * Loop through the task entries in the database * (1) Update on the server all that have a status of "accepted", "rejected" or "submitted" * (2) Send details on submitted tasks, such as where they were completed and optionally the trace of user movements, to the server */ private void updateTaskStatusToServer() throws Exception { DefaultHttpClient client = new DefaultHttpClient(); HttpResponse getResponse = null; TaskResponse updateResponse = new TaskResponse(); updateResponse.forms = tr.forms; // Add credentials if (username != null && password != null) { client.getCredentialsProvider().setCredentials(new AuthScope(null, -1, null), new UsernamePasswordCredentials(username, password)); } // Add device id to response updateResponse.deviceId = new PropertyManager(Collect.getInstance().getApplicationContext()) .getSingularProperty(PropertyManager.DEVICE_ID_PROPERTY); // Get tasks that have not been synchronised ArrayList<TaskEntry> nonSynchTasks = new ArrayList<TaskEntry>(); Utilities.getTasks(nonSynchTasks, true); /* * Set updates to task status */ updateResponse.taskAssignments = new ArrayList<TaskAssignment>(); // Updates to task status for (TaskEntry t : nonSynchTasks) { if (t.taskStatus != null && t.isSynced.equals(Utilities.STATUS_SYNC_NO)) { TaskAssignment ta = new TaskAssignment(); ta.assignment = new Assignment(); ta.assignment.assignment_id = (int) t.assId; ta.assignment.dbId = (int) t.id; ta.assignment.assignment_status = t.taskStatus; updateResponse.taskAssignments.add(ta); } } /* * Set details on submitted tasks */ if (tr.settings != null && tr.settings.ft_send_trail) { updateResponse.taskCompletionInfo = new ArrayList<TaskCompletionInfo>(); // Details on completed tasks for (TaskEntry t : nonSynchTasks) { if ((t.taskStatus.equals(Utilities.STATUS_T_SUBMITTED) || t.taskStatus.equals(Utilities.STATUS_T_CLOSED)) && t.isSynced.equals(Utilities.STATUS_SYNC_NO)) { TaskCompletionInfo tci = new TaskCompletionInfo(); tci.actFinish = t.actFinish; tci.lat = t.actLat; tci.lon = t.actLon; tci.ident = t.ident; tci.uuid = t.uuid; updateResponse.taskCompletionInfo.add(tci); } } // Get Points updateResponse.userTrail = new ArrayList<PointEntry>(100); TraceUtilities.getPoints(updateResponse.userTrail); } // Call the service String taskURL = serverUrl + "/surveyKPI/myassignments"; HttpPost postRequest = new HttpPost(taskURL); ArrayList<NameValuePair> postParameters = new ArrayList<NameValuePair>(); Gson gson = new GsonBuilder().disableHtmlEscaping().setDateFormat("yyyy-MM-dd").create(); String resp = gson.toJson(updateResponse); postParameters.add(new BasicNameValuePair("assignInput", resp)); postRequest.setEntity(new UrlEncodedFormEntity(postParameters)); getResponse = client.execute(postRequest); int statusCode = getResponse.getStatusLine().getStatusCode(); if (statusCode != HttpStatus.SC_OK) { Log.w(getClass().getSimpleName(), "Error:" + statusCode + " for URL " + taskURL); } else { Log.w("updateTaskStatusToServer", "Status updated"); for (TaskAssignment ta : updateResponse.taskAssignments) { Utilities.setTaskSynchronized((long) ta.assignment.dbId); // Mark the task status as synchronised } TraceUtilities.deleteSource(); } } /* * Loop through the entries from the source * (1) Add entries that have a status of "new", "pending" or "accepted" and are not already on the phone * (2) Update the status of database entries where the source status is set to "cancelled" */ private void addAndUpdateEntries() throws Exception { if (tr.taskAssignments != null) { for (TaskAssignment ta : tr.taskAssignments) { if (isCancelled()) { throw new CancelException("cancelled"); } ; // Return if the user cancels if (ta.task.type.equals("xform")) { Assignment assignment = ta.assignment; Log.i(getClass().getSimpleName(), "Task: " + assignment.assignment_id + " Status:" + assignment.assignment_status + " Mode:" + ta.task.assignment_mode + " Address: " + ta.task.address + " Form: " + ta.task.form_id + " version: " + ta.task.form_version + " Type: " + ta.task.type + "Assignee: " + assignment.assignee + "Username: " + username); // Find out if this task is already on the phone TaskStatus ts = taskMap.get(Long.valueOf((long) assignment.assignment_id)); if (ts == null) { Log.i(getClass().getSimpleName(), "New task: " + assignment.assignment_id); // New task if (assignment.assignment_status.equals(Utilities.STATUS_T_ACCEPTED)) { // Ensure the form and instance data are available on the phone // First make sure the initial_data url is sensible (ie null or a URL) if (ta.task.initial_data != null && !ta.task.initial_data.startsWith("http")) { ta.task.initial_data = null; } // Add instance data ManageForm mf = new ManageForm(); ManageFormResponse mfr = mf.insertInstance(ta, assignment.assignment_id, source); if (!mfr.isError) { results.put(ta.task.title, "Created"); } else { results.put(ta.task.title, "Creation failed: " + mfr.statusMsg); } } } else { // Existing task Log.i(getClass().getSimpleName(), "Existing Task: " + assignment.assignment_id + " : " + assignment.assignment_status); if (assignment.assignment_status.equals(Utilities.STATUS_T_CANCELLED) && !ts.status.equals(Utilities.STATUS_T_CANCELLED)) { Utilities.setStatusForAssignment(assignment.assignment_id, assignment.assignment_status); results.put(ta.task.title, assignment.assignment_status); } } } // end process for xform task } // end tasks loop } return; } /* * Synchronise the forms on the server with those on the phone * (1) Download forms on the server that are not on the phone * (2) Delete forms not on the server or older versions of forms * unless there is an uncompleted data instance using that form */ private HashMap<FormDetails, String> synchroniseForms(List<FormLocator> forms) throws Exception { HashMap<FormDetails, String> dfResults = null; if (forms == null) { publishProgress("No forms to download"); } else { HashMap<String, String> formMap = new HashMap<String, String>(); ManageForm mf = new ManageForm(); ArrayList<FormDetails> toDownload = new ArrayList<FormDetails>(); // Create an array of ODK form details for (FormLocator form : forms) { String formVersionString = String.valueOf(form.version); ManageFormDetails mfd = mf.getFormDetails(form.ident, formVersionString, source); // Get the form details Log.i("DownloadTasksTask", "+++ Form: " + form.ident + ":" + formVersionString); if (!mfd.exists) { Log.i("DownloadTasksTask", "+++ Form does not exist: " + form.ident + ":" + formVersionString); form.url = serverUrl + "/formXML?key=" + form.ident; // Set the form url from the server address and form ident if (form.hasManifest) { form.manifestUrl = serverUrl + "/xformsManifest?key=" + form.ident; } FormDetails fd = new FormDetails(form.name, form.url, form.manifestUrl, form.ident, formVersionString); toDownload.add(fd); } // Store a hashmap of new forms so we can delete existing forms not in the list String entryHash = form.ident + "_v_" + form.version; formMap.put(entryHash, entryHash); } DownloadFormsTask downloadFormsTask = new DownloadFormsTask(); publishProgress("Downloading " + toDownload.size() + " forms"); Log.i(getClass().getSimpleName(), "Downloading " + toDownload.size() + " forms"); downloadFormsTask.setDownloaderListener((FormDownloaderListener) mStateListener); dfResults = downloadFormsTask.doInBackground(toDownload); // Not in background as called directly // Delete any forms no longer required mf.deleteForms(formMap, results); } return dfResults; } /* * Return true if the passed in instance file is in the odk instance database * Assume that if it has been deleted from the database then it can't be sent although * it is probably still on the sdcard */ boolean instanceExists(String instancePath) { boolean exists = true; // Get the provider URI of the instance String where = InstanceColumns.INSTANCE_FILE_PATH + "=?"; String[] whereArgs = { instancePath }; ContentResolver cr = Collect.getInstance().getContentResolver(); Cursor cInstanceProvider = cr.query(InstanceColumns.CONTENT_URI, null, where, whereArgs, null); if (cInstanceProvider.getCount() != 1) { Log.e("MainListActivity:completeTask", "Unique instance not found: count is:" + cInstanceProvider.getCount()); exists = false; } cInstanceProvider.close(); return exists; } }