/* * Copyright (C) 2009 University of Washington * * 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.odk.collect.android.tasks; import org.apache.http.client.HttpClient; import org.apache.http.protocol.HttpContext; import org.javarosa.xform.parse.XFormParser; import org.kxml2.kdom.Element; import org.odk.collect.android.R; import org.odk.collect.android.application.Collect; import org.odk.collect.android.listeners.FormListDownloaderListener; import org.odk.collect.android.logic.FormDetails; import org.odk.collect.android.preferences.PreferencesActivity; import org.odk.collect.android.utilities.DocumentFetchResult; import org.odk.collect.android.utilities.WebUtils; import android.content.SharedPreferences; import android.os.AsyncTask; import android.preference.PreferenceManager; import android.util.Log; import java.util.HashMap; /** * Background task for downloading forms from urls or a formlist from a url. We overload this task a * bit so that we don't have to keep track of two separate downloading tasks and it simplifies * interfaces. If LIST_URL is passed to doInBackground(), we fetch a form list. If a hashmap * containing form/url pairs is passed, we download those forms. * * @author carlhartung */ public class DownloadFormListTask extends AsyncTask<Void, String, HashMap<String, FormDetails>> { private static final String t = "DownloadFormsTask"; // used to store error message if one occurs public static final String DL_ERROR_MSG = "dlerrormessage"; public static final String DL_AUTH_REQUIRED = "dlauthrequired"; private FormListDownloaderListener mStateListener; private static final String NAMESPACE_OPENROSA_ORG_XFORMS_XFORMS_LIST = "http://openrosa.org/xforms/xformsList"; private boolean isXformsListNamespacedElement(Element e) { return e.getNamespace().equalsIgnoreCase(NAMESPACE_OPENROSA_ORG_XFORMS_XFORMS_LIST); } /* * (non-Javadoc) * @see android.os.AsyncTask#doInBackground(java.lang.Object[]) */ @Override protected HashMap<String, FormDetails> doInBackground(Void... values) { SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(Collect.getInstance().getBaseContext()); String downloadListUrl = settings.getString(PreferencesActivity.KEY_SERVER_URL, Collect.getInstance().getString(R.string.default_server_url)); String downloadPath = settings.getString(PreferencesActivity.KEY_FORMLIST_URL, "/formlist"); downloadListUrl += downloadPath; String auth = settings.getString(PreferencesActivity.KEY_AUTH, ""); // We populate this with available forms from the specified server. // <formname, details> HashMap<String, FormDetails> formList = new HashMap<String, FormDetails>(); // get shared HttpContext so that authentication and cookies are retained. HttpContext localContext = Collect.getInstance().getHttpContext(); HttpClient httpclient = WebUtils.createHttpClient(WebUtils.CONNECTION_TIMEOUT); DocumentFetchResult result = WebUtils.getXmlDocument(downloadListUrl, localContext, httpclient, auth); // If we can't get the document, return the error, cancel the task if (result.errorMessage != null) { if (result.responseCode == 401) { formList.put(DL_AUTH_REQUIRED, new FormDetails(result.errorMessage)); } else { formList.put(DL_ERROR_MSG, new FormDetails(result.errorMessage)); } return formList; } if (result.isOpenRosaResponse) { // Attempt OpenRosa 1.0 parsing Element xformsElement = result.doc.getRootElement(); if (!xformsElement.getName().equals("xforms")) { String error = "root element is not <xforms> : " + xformsElement.getName(); Log.e(t, "Parsing OpenRosa reply -- " + error); formList.put( DL_ERROR_MSG, new FormDetails(Collect.getInstance().getString( R.string.parse_openrosa_formlist_failed, error))); return formList; } String namespace = xformsElement.getNamespace(); if (!isXformsListNamespacedElement(xformsElement)) { String error = "root element namespace is incorrect:" + namespace; Log.e(t, "Parsing OpenRosa reply -- " + error); formList.put( DL_ERROR_MSG, new FormDetails(Collect.getInstance().getString( R.string.parse_openrosa_formlist_failed, error))); return formList; } int nElements = xformsElement.getChildCount(); for (int i = 0; i < nElements; ++i) { if (xformsElement.getType(i) != Element.ELEMENT) { // e.g., whitespace (text) continue; } Element xformElement = (Element) xformsElement.getElement(i); if (!isXformsListNamespacedElement(xformElement)) { // someone else's extension? continue; } String name = xformElement.getName(); if (!name.equalsIgnoreCase("xform")) { // someone else's extension? continue; } // this is something we know how to interpret String formId = null; String formName = null; String majorMinorVersion = null; String description = null; String downloadUrl = null; String manifestUrl = null; // don't process descriptionUrl int fieldCount = xformElement.getChildCount(); for (int j = 0; j < fieldCount; ++j) { if (xformElement.getType(j) != Element.ELEMENT) { // whitespace continue; } Element child = xformElement.getElement(j); if (!isXformsListNamespacedElement(child)) { // someone else's extension? continue; } String tag = child.getName(); if (tag.equals("formID")) { formId = XFormParser.getXMLText(child, true); if (formId != null && formId.length() == 0) { formId = null; } } else if (tag.equals("name")) { formName = XFormParser.getXMLText(child, true); if (formName != null && formName.length() == 0) { formName = null; } } else if (tag.equals("majorMinorVersion")) { majorMinorVersion = XFormParser.getXMLText(child, true); if (majorMinorVersion != null && majorMinorVersion.length() == 0) { majorMinorVersion = null; } } else if (tag.equals("descriptionText")) { description = XFormParser.getXMLText(child, true); if (description != null && description.length() == 0) { description = null; } } else if (tag.equals("downloadUrl")) { downloadUrl = XFormParser.getXMLText(child, true); if (downloadUrl != null && downloadUrl.length() == 0) { downloadUrl = null; } } else if (tag.equals("manifestUrl")) { manifestUrl = XFormParser.getXMLText(child, true); if (manifestUrl != null && manifestUrl.length() == 0) { manifestUrl = null; } } } if (formId == null || downloadUrl == null || formName == null) { String error = "Forms list entry " + Integer.toString(i) + " is missing one or more tags: formId, name, or downloadUrl"; Log.e(t, "Parsing OpenRosa reply -- " + error); formList.clear(); formList.put( DL_ERROR_MSG, new FormDetails(Collect.getInstance().getString( R.string.parse_openrosa_formlist_failed, error))); return formList; } /* * TODO: We currently don't care about major/minor version. maybe someday we will. */ // Integer modelVersion = null; // Integer uiVersion = null; // try { // if (majorMinorVersion == null || majorMinorVersion.length() == 0) { // modelVersion = null; // uiVersion = null; // } else { // int idx = majorMinorVersion.indexOf("."); // if (idx == -1) { // modelVersion = Integer.parseInt(majorMinorVersion); // uiVersion = null; // } else { // modelVersion = Integer.parseInt(majorMinorVersion.substring(0, idx)); // uiVersion = // (idx == majorMinorVersion.length() - 1) ? null : Integer // .parseInt(majorMinorVersion.substring(idx + 1)); // } // } // } catch (Exception e) { // e.printStackTrace(); // String error = "Forms list entry " + Integer.toString(i) + // " has an invalid majorMinorVersion: " + majorMinorVersion; // Log.e(t, "Parsing OpenRosa reply -- " + error); // formList.clear(); // formList.put(DL_ERROR_MSG, new FormDetails( // Collect.getInstance().getString(R.string.parse_openrosa_formlist_failed, // error))); // return formList; // } formList.put(formId, new FormDetails(formName, downloadUrl, manifestUrl, formId)); } } else { // Aggregate 0.9.x mode... // populate HashMap with form names and urls Element formsElement = result.doc.getRootElement(); int formsCount = formsElement.getChildCount(); for (int i = 0; i < formsCount; ++i) { if (formsElement.getType(i) != Element.ELEMENT) { // whitespace continue; } Element child = formsElement.getElement(i); String tag = child.getName(); String formId = null; if (tag.equals("formID")) { formId = XFormParser.getXMLText(child, true); if (formId != null && formId.length() == 0) { formId = null; } } if (tag.equalsIgnoreCase("form")) { String formName = XFormParser.getXMLText(child, true); if (formName != null && formName.length() == 0) { formName = null; } String downloadUrl = child.getAttributeValue(null, "url"); downloadUrl = downloadUrl.trim(); if (downloadUrl != null && downloadUrl.length() == 0) { downloadUrl = null; } if (downloadUrl == null || formName == null) { String error = "Forms list entry " + Integer.toString(i) + " is missing form name or url attribute"; Log.e(t, "Parsing OpenRosa reply -- " + error); formList.clear(); formList.put( DL_ERROR_MSG, new FormDetails(Collect.getInstance().getString( R.string.parse_legacy_formlist_failed, error))); return formList; } formList.put(formName, new FormDetails(formName, downloadUrl, null, formName)); } } } return formList; } /* * (non-Javadoc) * @see android.os.AsyncTask#onPostExecute(java.lang.Object) */ @Override protected void onPostExecute(HashMap<String, FormDetails> value) { synchronized (this) { if (mStateListener != null) { mStateListener.formListDownloadingComplete(value); } } } public void setDownloaderListener(FormListDownloaderListener sl) { synchronized (this) { mStateListener = sl; } } }