From 98b1bd7eb47bc5452ba131603188ad0416ef1d1d Mon Sep 17 00:00:00 2001 From: Inigo Lopez de Heredia Date: Fri, 15 May 2015 11:56:59 +0100 Subject: [PATCH 01/20] [#299] Split multiple barcodes on external input * Use any whitespace character for splitting barcodes --- .../flow/ui/view/BarcodeQuestionView.java | 30 ++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/org/akvo/flow/ui/view/BarcodeQuestionView.java b/app/src/main/java/org/akvo/flow/ui/view/BarcodeQuestionView.java index 61ed786f3..e44c5ecd5 100644 --- a/app/src/main/java/org/akvo/flow/ui/view/BarcodeQuestionView.java +++ b/app/src/main/java/org/akvo/flow/ui/view/BarcodeQuestionView.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010-2012 Stichting Akvo (Akvo Foundation) + * Copyright (C) 2010-2015 Stichting Akvo (Akvo Foundation) * * This file is part of Akvo FLOW. * @@ -19,7 +19,9 @@ import android.content.Context; import android.content.DialogInterface; import android.os.Bundle; +import android.text.Editable; import android.text.TextUtils; +import android.text.TextWatcher; import android.view.LayoutInflater; import android.view.View; import android.view.View.OnClickListener; @@ -66,6 +68,32 @@ private void init() { mAddBtn = (ImageButton)findViewById(R.id.add_btn); mInputText = (EditText)findViewById(R.id.input_text); + if (mMultiple) { + mInputText.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + } + + @Override + public void afterTextChanged(Editable s) { + String[] tokens = s.toString().split("\\s+", -1); + if (tokens.length > 1) { + for (int i=0; i Date: Thu, 11 Jun 2015 16:07:23 +0100 Subject: [PATCH 02/20] [#304] Store survey id in ZipFileData * We'll need this ID in the file transmission data * Small clean up --- .../akvo/flow/service/DataSyncService.java | 196 +++++++++--------- 1 file changed, 96 insertions(+), 100 deletions(-) diff --git a/app/src/main/java/org/akvo/flow/service/DataSyncService.java b/app/src/main/java/org/akvo/flow/service/DataSyncService.java index b0efd6be0..c0726d595 100644 --- a/app/src/main/java/org/akvo/flow/service/DataSyncService.java +++ b/app/src/main/java/org/akvo/flow/service/DataSyncService.java @@ -106,6 +106,7 @@ public class DataSyncService extends IntentService { private static final String DEVICE_NOTIFICATION_PATH = "/devicenotification"; private static final String NOTIFICATION_PATH = "/processor?action="; private static final String FILENAME_PARAM = "&fileName="; + private static final String FORMID_PARAM = "&formID="; private static final String DATA_CONTENT_TYPE = "application/zip"; private static final String IMAGE_CONTENT_TYPE = "image/jpeg"; @@ -128,19 +129,26 @@ public DataSyncService() { @Override protected void onHandleIntent(Intent intent) { - Thread.setDefaultUncaughtExceptionHandler(PersistentUncaughtExceptionHandler.getInstance()); + try { + mProps = new PropertyUtil(getResources()); + mDatabase = new SurveyDbAdapter(this); + mDatabase.open(); - mProps = new PropertyUtil(getResources()); - mDatabase = new SurveyDbAdapter(this); - mDatabase.open(); + exportSurveys();// Create zip files, if necessary - exportSurveys();// Create zip files, if necessary + if (StatusUtil.hasDataConnection(this)) { + syncFiles();// Sync everything + } - if (StatusUtil.hasDataConnection(this)) { - syncFiles();// Sync everything + mDatabase.close(); + } catch (Exception e) { + Log.e(TAG, e.getMessage()); + PersistentUncaughtExceptionHandler.recordException(e); + } finally { + if (mDatabase != null) { + mDatabase.close(); + } } - - mDatabase.close(); } // ================================================================= // @@ -207,17 +215,21 @@ private long[] getUnexportedSurveys() { } private ZipFileData formZip(long surveyInstanceId) { - ZipFileData zipFileData = new ZipFileData(); - StringBuilder jsonBuf = new StringBuilder(); + try { + ZipFileData zipFileData = new ZipFileData(); + // Process form instance data and collect image filenames + FormInstance formInstance = processFormInstance(surveyInstanceId, zipFileData.imagePaths); - // Hold the responses in the StringBuilder - String uuid = processSurveyData(surveyInstanceId, jsonBuf, zipFileData.imagePaths); + if (formInstance == null) { + return null; + } - // THe filename will match the Survey Instance UUID - File zipFile = getSurveyInstanceFile(uuid); + // Serialize form instance as JSON + zipFileData.data = new ObjectMapper().writeValueAsString(formInstance); - // Write the data into the zip file - try { + File zipFile = getSurveyInstanceFile(zipFileData.uuid);// The filename will match the Survey Instance UUID + + // Write the data into the zip file String fileName = zipFile.getAbsolutePath();// Will normalize filename. zipFileData.filename = fileName; Log.i(TAG, "Creating zip file: " + fileName); @@ -225,11 +237,11 @@ private ZipFileData formZip(long surveyInstanceId) { CheckedOutputStream checkedOutStream = new CheckedOutputStream(fout, new Adler32()); ZipOutputStream zos = new ZipOutputStream(checkedOutStream); - writeTextToZip(zos, jsonBuf.toString(), SURVEY_DATA_FILE_JSON); + writeTextToZip(zos, zipFileData.data, SURVEY_DATA_FILE_JSON); String signingKeyString = mProps.getProperty(SIGNING_KEY_PROP); if (!StringUtil.isNullOrEmpty(signingKeyString)) { MessageDigest sha1Digest = MessageDigest.getInstance("SHA1"); - byte[] digest = sha1Digest.digest(jsonBuf.toString().getBytes("UTF-8")); + byte[] digest = sha1Digest.digest(zipFileData.data.getBytes("UTF-8")); SecretKeySpec signingKey = new SecretKeySpec(signingKeyString.getBytes("UTF-8"), SIGNING_ALGORITHM); Mac mac = Mac.getInstance(SIGNING_ALGORITHM); @@ -242,13 +254,12 @@ private ZipFileData formZip(long surveyInstanceId) { final String checksum = "" + checkedOutStream.getChecksum().getValue(); zos.close(); Log.i(TAG, "Closed zip output stream for file: " + fileName + ". Checksum: " + checksum); + return zipFileData; } catch (IOException | NoSuchAlgorithmException | InvalidKeyException e) { PersistentUncaughtExceptionHandler.recordException(e); Log.e(TAG, e.getMessage()); - zipFileData = null; + return null; } - - return zipFileData; } /** @@ -271,96 +282,78 @@ private void writeTextToZip(ZipOutputStream zos, String text, } /** - * iterate over the survey data returned from the database and populate the - * string builder and collections passed in with the requisite information. - * - * @param surveyInstanceId - Survey Instance Id - * @param jsonBuf - IN param. After execution this will contain the data to be sent - * @param imagePaths - IN param. After execution this will contain the list of photo paths to send - * @return Survey Instance UUID + * Iterate over the survey data returned from the database and populate the + * ZipFileData information, setting the UUID, Survey ID, image paths, and String data. */ - private String processSurveyData(long surveyInstanceId, StringBuilder jsonBuf, List imagePaths) { + private FormInstance processFormInstance(long surveyInstanceId, List imagePaths) throws IOException { FormInstance formInstance = new FormInstance(); List responses = new ArrayList<>(); - String uuid = null; Cursor data = mDatabase.getResponsesData(surveyInstanceId); - if (data != null) { - if (data.moveToFirst()) { - Log.i(TAG, "There is data to send. Forming contents"); - String deviceIdentifier = mDatabase.getPreference(ConstantUtil.DEVICE_IDENT_KEY); - if (deviceIdentifier == null) { - deviceIdentifier = "unset"; - } else { - deviceIdentifier = cleanVal(deviceIdentifier); + if (data != null && data.moveToFirst()) { + String deviceIdentifier = mDatabase.getPreference(ConstantUtil.DEVICE_IDENT_KEY); + if (deviceIdentifier == null) { + deviceIdentifier = "unset"; + } else { + deviceIdentifier = cleanVal(deviceIdentifier); + } + // evaluate indices once, outside the loop + int survey_fk_col = data.getColumnIndexOrThrow(SurveyInstanceColumns.SURVEY_ID); + int question_fk_col = data.getColumnIndexOrThrow(ResponseColumns.QUESTION_ID); + int answer_type_col = data.getColumnIndexOrThrow(ResponseColumns.TYPE); + int answer_col = data.getColumnIndexOrThrow(ResponseColumns.ANSWER); + int disp_name_col = data.getColumnIndexOrThrow(UserColumns.NAME); + int email_col = data.getColumnIndexOrThrow(UserColumns.EMAIL); + int submitted_date_col = data.getColumnIndexOrThrow(SurveyInstanceColumns.SUBMITTED_DATE); + int uuid_col = data.getColumnIndexOrThrow(SurveyInstanceColumns.UUID); + int duration_col = data.getColumnIndexOrThrow(SurveyInstanceColumns.DURATION); + int localeId_col = data.getColumnIndexOrThrow(SurveyInstanceColumns.RECORD_ID); + // Note: No need to query the surveyInstanceId, we already have that value + + do { + // Sanitize answer value. No newlines or tabs! + String value = data.getString(answer_col); + if (value != null) { + value = value.replace("\n", SPACE); + value = value.replace(DELIMITER, SPACE); + value = value.trim(); + } + // never send empty answers + if (value == null || value.length() == 0) { + continue; + } + final long submitted_date = data.getLong(submitted_date_col); + final long surveyal_time = (data.getLong(duration_col)) / 1000; + + if (formInstance.getUUID() == null) { + formInstance.setUUID(data.getString(uuid_col)); + formInstance.setFormId(data.getLong(survey_fk_col));// FormInstance uses a number for this attr + formInstance.setDataPointId(data.getString(localeId_col)); + formInstance.setDeviceId(deviceIdentifier); + formInstance.setSubmissionDate(submitted_date); + formInstance.setDuration(surveyal_time); + formInstance.setUsername(cleanVal(data.getString(disp_name_col))); + formInstance.setEmail(cleanVal(data.getString(email_col))); } - // evaluate indices once, outside the loop - int survey_fk_col = data.getColumnIndexOrThrow(SurveyInstanceColumns.SURVEY_ID); - int question_fk_col = data.getColumnIndexOrThrow(ResponseColumns.QUESTION_ID); - int answer_type_col = data.getColumnIndexOrThrow(ResponseColumns.TYPE); - int answer_col = data.getColumnIndexOrThrow(ResponseColumns.ANSWER); - int disp_name_col = data.getColumnIndexOrThrow(UserColumns.NAME); - int email_col = data.getColumnIndexOrThrow(UserColumns.EMAIL); - int submitted_date_col = data.getColumnIndexOrThrow(SurveyInstanceColumns.SUBMITTED_DATE); - int uuid_col = data.getColumnIndexOrThrow(SurveyInstanceColumns.UUID); - int duration_col = data.getColumnIndexOrThrow(SurveyInstanceColumns.DURATION); - int localeId_col = data.getColumnIndexOrThrow(SurveyInstanceColumns.RECORD_ID); - // Note: No need to query the surveyInstanceId, we already have that value - - do { - // Sanitize answer value. No newlines or tabs! - String value = data.getString(answer_col); - if (value != null) { - value = value.replace("\n", SPACE); - value = value.replace(DELIMITER, SPACE); - value = value.trim(); - } - // never send empty answers - if (value == null || value.length() == 0) { - continue; - } - final long submitted_date = data.getLong(submitted_date_col); - final long surveyal_time = (data.getLong(duration_col)) / 1000; - - if (uuid == null) { - uuid = data.getString(uuid_col);// Parse it just once - - formInstance.setUUID(uuid); - formInstance.setFormId(data.getLong(survey_fk_col)); - formInstance.setDataPointId(data.getString(localeId_col)); - formInstance.setDeviceId(deviceIdentifier); - formInstance.setSubmissionDate(submitted_date); - formInstance.setDuration(surveyal_time); - formInstance.setUsername(cleanVal(data.getString(disp_name_col))); - formInstance.setEmail(cleanVal(data.getString(email_col))); - } - String type = data.getString(answer_type_col); - if (ConstantUtil.IMAGE_RESPONSE_TYPE.equals(type) - || ConstantUtil.VIDEO_RESPONSE_TYPE.equals(type)) { - imagePaths.add(value); - } + String type = data.getString(answer_type_col); + if (ConstantUtil.IMAGE_RESPONSE_TYPE.equals(type) + || ConstantUtil.VIDEO_RESPONSE_TYPE.equals(type)) { + imagePaths.add(value); + } - Response response = new Response(); - response.setQuestionId(data.getString(question_fk_col)); - response.setAnswerType(type); - response.setValue(value); - responses.add(response); - } while (data.moveToNext()); - } + Response response = new Response(); + response.setQuestionId(data.getString(question_fk_col)); + response.setAnswerType(type); + response.setValue(value); + responses.add(response); + } while (data.moveToNext()); + formInstance.setResponses(responses); data.close(); } - formInstance.setResponses(responses); - ObjectMapper mapper = new ObjectMapper(); - try { - jsonBuf.append(mapper.writeValueAsString(formInstance)); - } catch (JsonProcessingException e) { - Log.e(TAG, e.getMessage()); - } - - return uuid; + return formInstance; } // replace troublesome chars in user-provided values @@ -694,7 +687,10 @@ private void displaySyncedNotification(int syncedForms, int failedForms) { * */ class ZipFileData { + String uuid = null; + String surveyId = null; String filename = null; + String data = null; List imagePaths = new ArrayList(); } From 85dec5930b382d61accefec4c59c7d8c0f8d2f7a Mon Sep 17 00:00:00 2001 From: Inigo Lopez de Heredia Date: Thu, 11 Jun 2015 16:43:30 +0100 Subject: [PATCH 03/20] [#304] Attach formId to GAE notifications request * Store formId in the SQLite database * Handle DB updagrde * Send formId in the HTTP request --- .../org/akvo/flow/dao/SurveyDbAdapter.java | 35 ++++++++++++------- .../akvo/flow/domain/FileTransmission.java | 8 +++++ .../akvo/flow/service/DataSyncService.java | 19 +++++----- 3 files changed, 42 insertions(+), 20 deletions(-) diff --git a/app/src/main/java/org/akvo/flow/dao/SurveyDbAdapter.java b/app/src/main/java/org/akvo/flow/dao/SurveyDbAdapter.java index 6a8299bce..9b9e4e6bd 100644 --- a/app/src/main/java/org/akvo/flow/dao/SurveyDbAdapter.java +++ b/app/src/main/java/org/akvo/flow/dao/SurveyDbAdapter.java @@ -125,6 +125,7 @@ public interface SurveyInstanceColumns { public interface TransmissionColumns { String _ID = "_id"; String SURVEY_INSTANCE_ID = "survey_instance_id"; + String SURVEY_ID = "survey_id"; String FILENAME = "filename"; String STATUS = "status";// separate table/constants? String START_DATE = "start_date";// do we really need this column? @@ -208,7 +209,8 @@ public interface TransmissionStatus { private static final int VER_LAUNCH = 78;// App refactor version. Start from scratch private static final int VER_FORM_SUBMITTER = 79; - private static final int DATABASE_VERSION = VER_FORM_SUBMITTER; + private static final int VER_FORM_DEL_CHECK = 80; + private static final int DATABASE_VERSION = VER_FORM_DEL_CHECK; private final Context context; @@ -296,6 +298,7 @@ public void onCreate(SQLiteDatabase db) { db.execSQL("CREATE TABLE " + Tables.TRANSMISSION + " (" + TransmissionColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," + TransmissionColumns.SURVEY_INSTANCE_ID + " INTEGER NOT NULL," + + TransmissionColumns.SURVEY_ID + " TEXT," + TransmissionColumns.FILENAME + " TEXT," + TransmissionColumns.STATUS + " INTEGER," + TransmissionColumns.START_DATE + " INTEGER," @@ -332,6 +335,9 @@ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { case VER_LAUNCH: db.execSQL("ALTER TABLE " + Tables.SURVEY_INSTANCE + " ADD COLUMN " + SurveyInstanceColumns.SUBMITTER + " TEXT"); + case VER_FORM_SUBMITTER: + db.execSQL("ALTER TABLE " + Tables.TRANSMISSION + + " ADD COLUMN " + TransmissionColumns.SURVEY_ID + " TEXT"); version = DATABASE_VERSION; } @@ -930,8 +936,8 @@ public void deleteResponses(String surveyInstanceId) { public void deleteSurveyInstance(String surveyInstanceId) { deleteResponses(surveyInstanceId); database.delete(Tables.SURVEY_INSTANCE, SurveyInstanceColumns._ID + "=?", - new String[] { - surveyInstanceId + new String[]{ + surveyInstanceId }); } @@ -943,20 +949,21 @@ public void deleteSurveyInstance(String surveyInstanceId) { */ public void deleteResponse(long surveyInstanceId, String questionId) { database.delete(Tables.RESPONSE, ResponseColumns.SURVEY_INSTANCE_ID + "= ? AND " - + ResponseColumns.QUESTION_ID + "= ?", new String[] { + + ResponseColumns.QUESTION_ID + "= ?", new String[]{ String.valueOf(surveyInstanceId), questionId }); } - public void createTransmission(long surveyInstanceId, String filename) { - createTransmission(surveyInstanceId, filename, TransmissionStatus.QUEUED); + public void createTransmission(long surveyInstanceId, String formID, String filename) { + createTransmission(surveyInstanceId, formID, filename, TransmissionStatus.QUEUED); } - public void createTransmission(long surveyInstanceId, String filename, int status) { + public void createTransmission(long surveyInstanceId, String formID, String filename, int status) { ContentValues values = new ContentValues(); values.put(TransmissionColumns.SURVEY_INSTANCE_ID, surveyInstanceId); + values.put(TransmissionColumns.SURVEY_ID, formID); values.put(TransmissionColumns.FILENAME, filename); values.put(TransmissionColumns.STATUS, status); if (TransmissionStatus.SYNCED == status) { @@ -999,6 +1006,7 @@ public List getFileTransmissions(Cursor cursor) { final int startCol = cursor.getColumnIndexOrThrow(TransmissionColumns.START_DATE); final int endCol = cursor.getColumnIndexOrThrow(TransmissionColumns.END_DATE); final int idCol = cursor.getColumnIndexOrThrow(TransmissionColumns._ID); + final int formIdCol = cursor.getColumnIndexOrThrow(TransmissionColumns.SURVEY_ID); final int surveyInstanceCol = cursor.getColumnIndexOrThrow(TransmissionColumns.SURVEY_INSTANCE_ID); final int fileCol = cursor.getColumnIndexOrThrow(TransmissionColumns.FILENAME); final int statusCol = cursor.getColumnIndexOrThrow(TransmissionColumns.STATUS); @@ -1007,6 +1015,7 @@ public List getFileTransmissions(Cursor cursor) { do { FileTransmission trans = new FileTransmission(); trans.setId(cursor.getLong(idCol)); + trans.setFormId(cursor.getString(formIdCol)); trans.setRespondentId(cursor.getLong(surveyInstanceCol)); trans.setFileName(cursor.getString(fileCol)); trans.setStatus(cursor.getInt(statusCol)); @@ -1032,8 +1041,9 @@ public List getFileTransmissions(long surveyInstanceId) { Cursor cursor = database.query(Tables.TRANSMISSION, new String[] { TransmissionColumns._ID, TransmissionColumns.SURVEY_INSTANCE_ID, - TransmissionColumns.STATUS, TransmissionColumns.FILENAME, - TransmissionColumns.START_DATE, TransmissionColumns.END_DATE + TransmissionColumns.SURVEY_ID, TransmissionColumns.STATUS, + TransmissionColumns.FILENAME, TransmissionColumns.START_DATE, + TransmissionColumns.END_DATE }, TransmissionColumns.SURVEY_INSTANCE_ID + " = ?", new String[] { String.valueOf(surveyInstanceId) }, @@ -1049,8 +1059,9 @@ public List getUnsyncedTransmissions() { Cursor cursor = database.query(Tables.TRANSMISSION, new String[] { TransmissionColumns._ID, TransmissionColumns.SURVEY_INSTANCE_ID, - TransmissionColumns.STATUS, TransmissionColumns.FILENAME, - TransmissionColumns.START_DATE, TransmissionColumns.END_DATE + TransmissionColumns.SURVEY_ID, TransmissionColumns.STATUS, + TransmissionColumns.FILENAME, TransmissionColumns.START_DATE, + TransmissionColumns.END_DATE }, TransmissionColumns.STATUS + " IN (?, ?, ?)", new String[] { @@ -1624,7 +1635,7 @@ public void syncSurveyInstances(List surveyInstances, String sur // The filename is a unique column in the transmission table, and as we do not have // a file to hold this data, we set the value to the instance UUID - createTransmission(id, surveyInstance.getUuid(), TransmissionStatus.SYNCED); + createTransmission(id, surveyInstance.getSurveyId(), surveyInstance.getUuid(), TransmissionStatus.SYNCED); } } diff --git a/app/src/main/java/org/akvo/flow/domain/FileTransmission.java b/app/src/main/java/org/akvo/flow/domain/FileTransmission.java index 87a503a6f..ee8bcb127 100644 --- a/app/src/main/java/org/akvo/flow/domain/FileTransmission.java +++ b/app/src/main/java/org/akvo/flow/domain/FileTransmission.java @@ -26,6 +26,7 @@ public class FileTransmission { private Long id; private Long respondentId; + private String formId; private String fileName; private Date startDate; private Date endDate; @@ -79,4 +80,11 @@ public void setEndDate(Date endDate) { this.endDate = endDate; } + public String getFormId() { + return formId; + } + + public void setFormId(String formId) { + this.formId = formId; + } } diff --git a/app/src/main/java/org/akvo/flow/service/DataSyncService.java b/app/src/main/java/org/akvo/flow/service/DataSyncService.java index c0726d595..b8c724c94 100644 --- a/app/src/main/java/org/akvo/flow/service/DataSyncService.java +++ b/app/src/main/java/org/akvo/flow/service/DataSyncService.java @@ -166,11 +166,11 @@ private void exportSurveys() { displayExportNotification(getDestName(zipFileData.filename)); // Create new entries in the transmission queue - mDatabase.createTransmission(id, zipFileData.filename); + mDatabase.createTransmission(id, zipFileData.formId, zipFileData.filename); updateSurveyStatus(id, SurveyInstanceStatus.EXPORTED); for (String image : zipFileData.imagePaths) { - mDatabase.createTransmission(id, image); + mDatabase.createTransmission(id, zipFileData.formId, image); } } } @@ -226,6 +226,8 @@ private ZipFileData formZip(long surveyInstanceId) { // Serialize form instance as JSON zipFileData.data = new ObjectMapper().writeValueAsString(formInstance); + zipFileData.uuid = formInstance.getUUID(); + zipFileData.formId = String.valueOf(formInstance.getFormId()); File zipFile = getSurveyInstanceFile(zipFileData.uuid);// The filename will match the Survey Instance UUID @@ -408,7 +410,7 @@ private void syncFiles() { for (int i = 0; i < totalFiles; i++) { FileTransmission transmission = transmissions.get(i); final long surveyInstanceId = transmission.getRespondentId(); - if (syncFile(transmission.getFileName(), transmission.getStatus(), serverBase)) { + if (syncFile(transmission.getFileName(), transmission.getFormId(), transmission.getStatus(), serverBase)) { syncedSurveys.add(surveyInstanceId); } else { unsyncedSurveys.add(surveyInstanceId); @@ -431,7 +433,7 @@ private void syncFiles() { } } - private boolean syncFile(String filename, int status, String serverBase) { + private boolean syncFile(String filename, String formId, int status, String serverBase) { if (TextUtils.isEmpty(filename)) { return false; } @@ -460,7 +462,7 @@ private boolean syncFile(String filename, int status, String serverBase) { if (ok && action != null) { // If action is not null, notify GAE back-end that data is available // TODO: Do we need to send the checksum? - ok = sendProcessingNotification(serverBase, action, destName); + ok = sendProcessingNotification(serverBase, formId, action, destName); } // Update database and display notification @@ -560,7 +562,7 @@ private void setFileTransmissionFailed(String filename) { int rows = mDatabase.updateTransmissionHistory(filename, TransmissionStatus.FAILED); if (rows == 0) { // Use a dummy "-1" as survey_instance_id, as the database needs that attribute - mDatabase.createTransmission(-1, filename, TransmissionStatus.FAILED); + mDatabase.createTransmission(-1, null, filename, TransmissionStatus.FAILED); } } @@ -582,9 +584,10 @@ private String getDeviceNotification(String serverBase) throws Exception { * @param fileName * @return */ - private boolean sendProcessingNotification(String serverBase, String action, String fileName) { + private boolean sendProcessingNotification(String serverBase, String formId, String action, String fileName) { boolean success = false; String url = serverBase + NOTIFICATION_PATH + action + + FORMID_PARAM + formId + FILENAME_PARAM + fileName + "&" + FlowApi.getDeviceParams(); try { HttpUtil.httpGet(url); @@ -688,7 +691,7 @@ private void displaySyncedNotification(int syncedForms, int failedForms) { */ class ZipFileData { String uuid = null; - String surveyId = null; + String formId = null; String filename = null; String data = null; List imagePaths = new ArrayList(); From c07abdef7f5ed566f758e2d75ccd02255b9f3a3f Mon Sep 17 00:00:00 2001 From: Inigo Lopez de Heredia Date: Fri, 12 Jun 2015 10:23:15 +0100 Subject: [PATCH 04/20] [#304] Display (unique) notification * Use form ID as notification ID, avoiding collisions --- .../org/akvo/flow/service/DataSyncService.java | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/org/akvo/flow/service/DataSyncService.java b/app/src/main/java/org/akvo/flow/service/DataSyncService.java index b8c724c94..d324c800c 100644 --- a/app/src/main/java/org/akvo/flow/service/DataSyncService.java +++ b/app/src/main/java/org/akvo/flow/service/DataSyncService.java @@ -41,6 +41,7 @@ import org.akvo.flow.dao.SurveyDbAdapter.TransmissionStatus; import org.akvo.flow.dao.SurveyDbAdapter.SurveyInstanceStatus; import org.akvo.flow.domain.FileTransmission; +import org.akvo.flow.exception.HttpException; import org.akvo.flow.exception.PersistentUncaughtExceptionHandler; import org.akvo.flow.util.Base64; import org.akvo.flow.util.ConstantUtil; @@ -52,6 +53,7 @@ import org.akvo.flow.util.StringUtil; import org.akvo.flow.util.ViewUtil; +import org.apache.http.HttpStatus; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; @@ -163,7 +165,7 @@ private void exportSurveys() { for (long id : getUnexportedSurveys()) { ZipFileData zipFileData = formZip(id); if (zipFileData != null) { - displayExportNotification(getDestName(zipFileData.filename)); + displayNotification(id, getString(R.string.exportcomplete), getDestName(zipFileData.filename)); // Create new entries in the transmission queue mDatabase.createTransmission(id, zipFileData.formId, zipFileData.filename); @@ -592,6 +594,13 @@ private boolean sendProcessingNotification(String serverBase, String formId, Str try { HttpUtil.httpGet(url); success = true; + } catch (HttpException e) { + if (e.getStatus() == HttpStatus.SC_NOT_FOUND) { + // This form has probably been deleted. + Log.e(TAG, "404 response for formId: " + formId); + displayNotification(Integer.valueOf(formId), + "Form " + formId + " does not exist", "It has probably been deleted"); + } } catch (Exception e) { Log.e(TAG, "GAE sync notification failed for file: " + fileName); } @@ -620,12 +629,10 @@ private void updateSurveyStatus(long surveyInstanceId, int status) { sendBroadcast(intentBroadcast); } - private void displayExportNotification(String filename) { - String text = getString(R.string.exportcomplete); - ViewUtil.fireNotification(text, filename, this, ConstantUtil.NOTIFICATION_DATA_SYNC, null); + private void displayNotification(long id, String title, String text) { + ViewUtil.fireNotification(title, text, this, (int)id, null); } - /** * Display a notification showing the up-to-date status of the sync * @param synced number of handled transmissions so far (either successful or not) From 9cdde6ddf90cf5d3e2734ac4e741b38b7f3aacf7 Mon Sep 17 00:00:00 2001 From: Inigo Lopez de Heredia Date: Fri, 12 Jun 2015 11:18:06 +0100 Subject: [PATCH 05/20] [#304] Clean up Javadoc --- .../akvo/flow/service/DataSyncService.java | 26 +++---------------- 1 file changed, 3 insertions(+), 23 deletions(-) diff --git a/app/src/main/java/org/akvo/flow/service/DataSyncService.java b/app/src/main/java/org/akvo/flow/service/DataSyncService.java index d324c800c..4c09bb1b6 100644 --- a/app/src/main/java/org/akvo/flow/service/DataSyncService.java +++ b/app/src/main/java/org/akvo/flow/service/DataSyncService.java @@ -26,7 +26,6 @@ import android.text.TextUtils; import android.util.Log; -import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import org.akvo.flow.R; @@ -267,13 +266,8 @@ private ZipFileData formZip(long surveyInstanceId) { } /** - * writes the contents of text to a zip entry within the Zip file behind zos + * Writes the contents of text to a zip entry within the Zip file behind zos * named fileName - * - * @param zos - * @param text - * @param fileName - * @throws java.io.IOException */ private void writeTextToZip(ZipOutputStream zos, String text, String fileName) throws IOException { @@ -543,9 +537,6 @@ private void checkMissingFiles(String serverBase) { /** * Given a json array, return the list of contained filenames, * formatting the path to match the structure of the sdcard's files. - * @param jFiles - * @return - * @throws JSONException */ private List parseFiles(JSONArray jFiles) throws JSONException { List files = new ArrayList(); @@ -580,11 +571,8 @@ private String getDeviceNotification(String serverBase) throws Exception { } /** - * sends a message to the service with the file name that was just uploaded + * Sends a message to the service with the file name that was just uploaded * so it can start processing the file - * - * @param fileName - * @return */ private boolean sendProcessingNotification(String serverBase, String formId, String action, String fileName) { boolean success = false; @@ -617,9 +605,6 @@ private static String getDestName(String filename) { return filename; } - /** - * - */ private void updateSurveyStatus(long surveyInstanceId, int status) { // First off, update the status mDatabase.updateSurveyStatus(surveyInstanceId, status); @@ -689,12 +674,7 @@ private void displaySyncedNotification(int syncedForms, int failedForms) { } /** - * Helper class to wrap zip file's meta-data.
- * It will contain: - *
    - *
  • filename
  • - *
  • Image Paths
  • - *
+ * Helper class to wrap zip file's meta-data */ class ZipFileData { String uuid = null; From b8f4dd95adb5f93b16423f463bc5677f46972017 Mon Sep 17 00:00:00 2001 From: Inigo Lopez de Heredia Date: Mon, 15 Jun 2015 16:17:02 +0100 Subject: [PATCH 06/20] [#304] Send synced forms list * Append form ids to the device notification query --- .../main/java/org/akvo/flow/service/DataSyncService.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/org/akvo/flow/service/DataSyncService.java b/app/src/main/java/org/akvo/flow/service/DataSyncService.java index 4c09bb1b6..87a006b83 100644 --- a/app/src/main/java/org/akvo/flow/service/DataSyncService.java +++ b/app/src/main/java/org/akvo/flow/service/DataSyncService.java @@ -566,7 +566,12 @@ private void setFileTransmissionFailed(String filename) { * @throws Exception */ private String getDeviceNotification(String serverBase) throws Exception { - String url = serverBase + DEVICE_NOTIFICATION_PATH + "?" + FlowApi.getDeviceParams(); + // Send the list of surveys we've got downloaded, getting notified of the deleted ones + StringBuilder surveyIds = new StringBuilder(); + for (String id : mDatabase.getSurveyIds()) { + surveyIds.append("&formId=" + id); + } + String url = serverBase + DEVICE_NOTIFICATION_PATH + "?" + FlowApi.getDeviceParams() + surveyIds.toString(); return HttpUtil.httpGet(url); } From e53208be8cec67dfd19f8bef3252c68c55e2f1e4 Mon Sep 17 00:00:00 2001 From: Inigo Lopez de Heredia Date: Tue, 16 Jun 2015 16:28:51 +0100 Subject: [PATCH 07/20] [#304] Display notification for deleted forms * Show one notification per form * Add formId() helper method --- .../akvo/flow/service/DataSyncService.java | 44 ++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/org/akvo/flow/service/DataSyncService.java b/app/src/main/java/org/akvo/flow/service/DataSyncService.java index 87a006b83..d99f9779e 100644 --- a/app/src/main/java/org/akvo/flow/service/DataSyncService.java +++ b/app/src/main/java/org/akvo/flow/service/DataSyncService.java @@ -528,6 +528,13 @@ private void checkMissingFiles(String serverBase) { setFileTransmissionFailed(filename); } } + + JSONArray jForms = jResponse.optJSONArray("deletedForms"); + if (jForms != null) { + for (int i=0; i Date: Tue, 16 Jun 2015 16:30:19 +0100 Subject: [PATCH 08/20] [#304] Delete form from the app * Once the user dismisses the notification, we delete the form * Create BroadcastReceiver --- app/src/main/AndroidManifest.xml | 5 +++ .../flow/broadcast/FormDeletedReceiver.java | 42 +++++++++++++++++++ .../akvo/flow/service/DataSyncService.java | 9 ++++ 3 files changed, 56 insertions(+) create mode 100644 app/src/main/java/org/akvo/flow/broadcast/FormDeletedReceiver.java diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 1ef9dddb7..af6eca315 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -103,6 +103,11 @@ + + + diff --git a/app/src/main/java/org/akvo/flow/broadcast/FormDeletedReceiver.java b/app/src/main/java/org/akvo/flow/broadcast/FormDeletedReceiver.java new file mode 100644 index 000000000..2fe5689fb --- /dev/null +++ b/app/src/main/java/org/akvo/flow/broadcast/FormDeletedReceiver.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2015 Stichting Akvo (Akvo Foundation) + * + * This file is part of Akvo FLOW. + * + * Akvo FLOW is free software: you can redistribute it and modify it under the terms of + * the GNU Affero General Public License (AGPL) as published by the Free Software Foundation, + * either version 3 of the License or any later version. + * + * Akvo FLOW 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 Affero General Public License included below for more details. + * + * The full license text can also be seen at . + */ + +package org.akvo.flow.broadcast; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.widget.Toast; + +import org.akvo.flow.dao.SurveyDbAdapter; +import org.akvo.flow.service.DataSyncService; +import org.akvo.flow.service.SurveyDownloadService; + +public class FormDeletedReceiver extends BroadcastReceiver { + public static final String FORM_ID = "formId"; + + public void onReceive(Context context, Intent intent) { + String formId = intent.getStringExtra(FORM_ID); + + SurveyDbAdapter db = new SurveyDbAdapter(context).open(); + db.deleteSurvey(formId, false); + db.close(); + + Toast.makeText(context, "Form " + formId + " has been removed", Toast.LENGTH_SHORT) + .show(); + SurveyDownloadService.sendBroadcastNotification(context); + } +} diff --git a/app/src/main/java/org/akvo/flow/service/DataSyncService.java b/app/src/main/java/org/akvo/flow/service/DataSyncService.java index d99f9779e..79d965fc8 100644 --- a/app/src/main/java/org/akvo/flow/service/DataSyncService.java +++ b/app/src/main/java/org/akvo/flow/service/DataSyncService.java @@ -33,6 +33,7 @@ import org.akvo.flow.api.S3Api; import org.akvo.flow.api.response.FormInstance; import org.akvo.flow.api.response.Response; +import org.akvo.flow.broadcast.FormDeletedReceiver; import org.akvo.flow.dao.SurveyDbAdapter; import org.akvo.flow.dao.SurveyDbAdapter.ResponseColumns; import org.akvo.flow.dao.SurveyDbAdapter.SurveyInstanceColumns; @@ -698,6 +699,14 @@ private void displayFormDeletedNotification(String formId) { .setContentText(text) .setTicker(text) .setOngoing(false); + + // Delete intent. Once the user dismisses the notification, we'll delete the form. + Intent intent = new Intent(this, FormDeletedReceiver.class); + intent.putExtra(FormDeletedReceiver.FORM_ID, formId); + PendingIntent pendingIntent = PendingIntent.getBroadcast(this, + notificationId, intent, 0); + builder.setDeleteIntent(pendingIntent); + // Dummy intent. Do nothing when clicked PendingIntent dummyIntent = PendingIntent.getActivity(this, 0, new Intent(), 0); builder.setContentIntent(dummyIntent); From 34ea976511c699c32be0ff98e9a4d6b6549aef54 Mon Sep 17 00:00:00 2001 From: Inigo Lopez de Heredia Date: Tue, 16 Jun 2015 16:31:15 +0100 Subject: [PATCH 09/20] [#304] Allow external components to trigger signal --- .../java/org/akvo/flow/service/SurveyDownloadService.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/org/akvo/flow/service/SurveyDownloadService.java b/app/src/main/java/org/akvo/flow/service/SurveyDownloadService.java index a90f57812..d92858f2e 100644 --- a/app/src/main/java/org/akvo/flow/service/SurveyDownloadService.java +++ b/app/src/main/java/org/akvo/flow/service/SurveyDownloadService.java @@ -93,7 +93,7 @@ public void onHandleIntent(Intent intent) { } } - sendBroadcastNotification(); + sendBroadcastNotification(this); } public void onCreate() { @@ -367,9 +367,9 @@ private void displayNotification(int synced, int failed, int total) { * This notification will be received in SurveyHomeActivity, in order to * refresh its data */ - private void sendBroadcastNotification() { - Intent intentBroadcast = new Intent(getString(R.string.action_surveys_sync)); - sendBroadcast(intentBroadcast); + public static void sendBroadcastNotification(Context context) { + Intent intentBroadcast = new Intent(context.getString(R.string.action_surveys_sync)); + context.sendBroadcast(intentBroadcast); } } From f007976688d7ca5475ed231672e1de1e15aed3ed Mon Sep 17 00:00:00 2001 From: Inigo Lopez de Heredia Date: Mon, 22 Jun 2015 16:07:57 +0100 Subject: [PATCH 10/20] [#307] Disable field on read-only mode --- .../main/java/org/akvo/flow/ui/view/FreetextQuestionView.java | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/java/org/akvo/flow/ui/view/FreetextQuestionView.java b/app/src/main/java/org/akvo/flow/ui/view/FreetextQuestionView.java index 8169aa7fa..fbf4d4040 100644 --- a/app/src/main/java/org/akvo/flow/ui/view/FreetextQuestionView.java +++ b/app/src/main/java/org/akvo/flow/ui/view/FreetextQuestionView.java @@ -64,6 +64,7 @@ private void init() { if (isReadOnly()) { mEditText.setFocusable(false); + mDoubleEntryText.setFocusable(false); } int maxLength = ValidationRule.DEFAULT_MAX_LENGTH; From 95702503390a6d5e58a450f1caac59ba5f202884 Mon Sep 17 00:00:00 2001 From: Inigo Lopez de Heredia Date: Thu, 25 Jun 2015 15:32:58 +0100 Subject: [PATCH 11/20] [#310] Always update survey groups --- .../org/akvo/flow/service/SurveyDownloadService.java | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/org/akvo/flow/service/SurveyDownloadService.java b/app/src/main/java/org/akvo/flow/service/SurveyDownloadService.java index a90f57812..ff8a2b96f 100644 --- a/app/src/main/java/org/akvo/flow/service/SurveyDownloadService.java +++ b/app/src/main/java/org/akvo/flow/service/SurveyDownloadService.java @@ -118,7 +118,10 @@ private void checkAndDownload(String[] surveyIds) throws IOException { surveys = checkForSurveys(serverBase); } - // if there are surveys for this device, see if we need them + // Update all survey groups + syncSurveyGroups(surveys); + + // Check synced versions, and omit up-to-date surveys surveys = databaseAdaptor.checkSurveyVersions(surveys); if (!surveys.isEmpty()) { @@ -127,7 +130,6 @@ private void checkAndDownload(String[] surveyIds) throws IOException { for (Survey survey : surveys) { try { downloadSurvey(survey); - databaseAdaptor.addSurveyGroup(survey.getSurveyGroup()); databaseAdaptor.saveSurvey(survey); String[] langs = LangsPreferenceUtil.determineLanguages(this, survey); databaseAdaptor.addLanguages(langs); @@ -153,6 +155,12 @@ private void checkAndDownload(String[] surveyIds) throws IOException { } } + private void syncSurveyGroups(List surveys) { + for (Survey s : surveys) { + databaseAdaptor.addSurveyGroup(s.getSurveyGroup()); + } + } + /** * Downloads the survey based on the ID and then updates the survey object * with the filename and location From 4cf4fd66fd41ce74352aaba23d5317a8bf4a9167 Mon Sep 17 00:00:00 2001 From: Inigo Lopez de Heredia Date: Fri, 26 Jun 2015 13:14:16 +0100 Subject: [PATCH 12/20] [#312] Reference single strings.xml file * Use single arrays.xml (No need to translate) --- app/src/main/res/values-es/arrays.xml | 22 ---------------------- app/src/main/res/values-es/strings.xml | 2 ++ app/src/main/res/values-fr/arrays.xml | 22 ---------------------- app/src/main/res/values/arrays.xml | 14 ++++++++------ app/src/main/res/values/strings.xml | 8 ++++++++ 5 files changed, 18 insertions(+), 50 deletions(-) delete mode 100644 app/src/main/res/values-es/arrays.xml delete mode 100644 app/src/main/res/values-fr/arrays.xml diff --git a/app/src/main/res/values-es/arrays.xml b/app/src/main/res/values-es/arrays.xml deleted file mode 100644 index ec0dbac64..000000000 --- a/app/src/main/res/values-es/arrays.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - PUNTOS DE DATOS - MAPA - - - - ENCUESTAS - RESPUESTAS - MAPA - - - - Fecha - Distancia - Estado - Nombre - - - \ No newline at end of file diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 333438294..1f03fa5bf 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -173,6 +173,8 @@ Version
Últimos 7 días: Hoy: Distancia: + PUNTOS DE DATOS + MAPA Punto sin nombre Última modificación: diff --git a/app/src/main/res/values-fr/arrays.xml b/app/src/main/res/values-fr/arrays.xml deleted file mode 100644 index 81120d8ef..000000000 --- a/app/src/main/res/values-fr/arrays.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - ENREGISTREMENTS - CARTE - - - - ENQUÊTES - RÉPONSES - CARTE - - - - Date - Distance - Status - Name - - - \ No newline at end of file diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml index 7bde822c7..38bd57b67 100644 --- a/app/src/main/res/values/arrays.xml +++ b/app/src/main/res/values/arrays.xml @@ -2,8 +2,8 @@ - DATA POINTS - MAP + @string/tab_datapoints + @string/tab_map @@ -13,10 +13,10 @@ - Date - Distance - Status - Name + @string/order_date + @string/order_distance + @string/order_status + @string/order_name @@ -24,11 +24,13 @@ English Español Français + Hindi en es fr + hi diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index cf963886e..1a26c2d02 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -172,11 +172,19 @@ Last 7 days: Today: Distance: + DATA POINTS + MAP + Date + Distance + Status + Name Unnamed data point Last Modified: Last Response: Cascade files are not downloaded + FORMS + HISTORY Queued In Progress From a482e5b9e14f2ef2ba85ac3810dbaa0d9a888db2 Mon Sep 17 00:00:00 2001 From: Inigo Lopez de Heredia Date: Mon, 29 Jun 2015 11:41:37 +0100 Subject: [PATCH 13/20] [#312] Update strings * Update EN, ES, FR strings. * Add Hindi translations. --- app/src/main/res/values-es/strings.xml | 8 + app/src/main/res/values-fr/strings.xml | 10 + app/src/main/res/values-hi/strings.xml | 230 ++++++++++++ app/src/main/res/values/strings.xml | 464 ++++++++++++------------- 4 files changed, 479 insertions(+), 233 deletions(-) create mode 100644 app/src/main/res/values-hi/strings.xml diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 1f03fa5bf..ce85efe79 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -98,6 +98,7 @@ Version
Cargando datos de la encuesta desde la tarjeta SD Datos cargados desde la tarjeta SD Ha ocurrido un error procesando el archivo zip bootstrap + Error: El formulario no pertenece a esta instancia FLOW Historial de transmisión Transmisión comenzada: Transmisión terminada: @@ -175,10 +176,17 @@ Version
Distancia: PUNTOS DE DATOS MAPA + Fecha + Distancia + Estado + Nombre Punto sin nombre Última modificación: Última respuesta: + Los recursos cascada no han sido descargados + ENCUESTAS + RESPUESTAS En espera En curso diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index cb985f8f8..be4186e8c 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -95,6 +95,7 @@ Charger les données de l\'enquête depuis la carte SD Chargement des données depuis la carte SD effectué Erreur lors de l\'exportation du fichier zip + Erreur: Ce formulaire n\'est pas dans cette instance. Historique des transmissions La transmission a commencé: La transmission est finie: @@ -170,10 +171,19 @@ Les 7 derniers jours: Aujourd\'hui: Distance: + ENREGISTREMENTS + CARTE + Date + Distance + Status + Nom Donnée non nommée Dernière modification Dernière réponse + Les fichiers de cascade ne sont pas télécharger + ENQUÊTES + RÉPONSES En attente En cours diff --git a/app/src/main/res/values-hi/strings.xml b/app/src/main/res/values-hi/strings.xml new file mode 100644 index 000000000..bfa8570a7 --- /dev/null +++ b/app/src/main/res/values-hi/strings.xml @@ -0,0 +1,230 @@ + + + खोज जारी है + Acc + खोज जारी है + तैयार + जगह नहीं है + कम जगह है + SD कार्ड भर चूका है , अधिक जानकारी के लिए जगह नहीं है. जारी रहने के लिए, SD कार्ड पर स्थान उपलब्द करें। + आपके SD पर %% जगह उपलब्द है. SD के भर जाने बाद आप और अधिक जानकारी जमा नहीं कर पाएंगे। + फोटो खींचे + वीडियो बनाएँ + जियोलोकशन जाँचे + लैटीट्ूड + लोंगीतुड + ऊंचाई + सुरक्षित करें + हटाएं + GPS इस कार्य के लिए सक्षम होना चाइये . Settings पे जाने के लिए OK करें . + GPS सक्षम होना चाहिए . Setting पे जाने के लिए OK करें. Use GPS Satellite चुनें और वापस जाने के लिए बैक बटन दबाएं . GPS सक्षम होने पर, Check Geo Location दबाकर अपनी स्तिधि जानिए . + मैनेज यूजर + रीसाइज़े लार्ज इमेजेज + सेटिंग्स + ऐड यूजर + नो यूजर + डिस्प्ले नाम + ईमेल एड्रेस + उपयोगकर्ता के नाम का चुनें .तथा नए उपयोगकर्ता के लिए, \'+\' को दबाएं + एडिट यूजर + डिलीट यूजर + मैनेज उसेर्स दबाकर, अपने नाम को चुनें + SD कार्ड पे डेटा भेज दिया गया है + प्रेफ्रेंसेस + सर्वे की भाषा तथा अन्य विकल्प की सूची + सिंक डेटा + सर्वे डेटा सिंक करें . इस कार्य करें के लिए मोबाइल या वाई -फाई का होना आवश्यक है + पावर मैनेजमेंट + बैटरी का नियंत्रण + सर्वे के दौरान, स्क्रीन को ओन रखें + सर्वे करते समय स्क्रीन को बंध न होने देता है. बैटरी की बचत के लिए,इसतमाल न करने की स्तिथि में स्क्रीन को बंद करना या एप्प से बहार निकलना आवशयक है + अबाउट + एप्प का वर्शन जाननें के लिए + अबाउट-Akvo FLOW एप्प + Akvo FLOW एप्प , Akvo फाउंडेशन के द्वारा विकसित तथा २०१०-२०१४ तक कॉपीराइटेड है + GPS स्टेटस + GPS स्टेटस को इस्तेमाल करने के लिए, इसका आपके फ़ोन या टेबलेट में स्तापित होना आवश्यक है + आपके उपकरण में GPS स्टेटस स्तापित नहीं है I इसे स्तापित करने के गूगल प्ले स्टोर इस्तमाल करैं I + कृपया विवरण डाले + सहायक पुस्तिका देखें + सहायक वीडियो देखें + सहायक चित्र देखें + सहायक विकल्प + प्रतिक्रिया को हटाएं + अन्य + कार्य के मध्य चयन किये गए यूज़र को जारी रखें + मोबाइल नेटवर्क का इस्तमाल करते हुए डेटा सिंक करें + सर्वे की भाषाएँ + अपने स्तिथि की जानकारी बेजें + बारकोड को स्कैन करें + स्कैनर आपके फ़ोन अथवा टेबलेट पे उपलब्ध नहीं है I एंड्राइड मार्किट से बारकोड स्कैनर एप्प उपलब्ध करें I + संख्या अति छोटी है I नुन्यतम संख्या डाले : + संख्या अति बड़ी है I अधिकतम संख्या डाले : + उत्तर संख्या में दें + डेटा सर्वर (निर्देश पर ही बदलें ) + सभी सर्वे को पुनः लोड करें + सभी नियुक्त सर्वे को हटाने और पुनः डाउनलोड करने के लिए (निर्देश पर ही प्रयोग करें ) + कृपया निश्चित करें + क्या आप सर्वे को हटा कर पुनः डाउनलोड करना चाहतें हैं ? + सर्वे जमा करें + सर्वे की प्रतिक्रयाएं सर्वर पे भेज दी जाएंगी + विकल्प + भाषाएं + फॉर्म डाउनलोड करें + किसी भी एक भाषा का चनाव करें + कृपया प्रतीक्षा करें + फॉर्म को डाउनलोड करें + स्वेच्छित सर्वे डाउनलोड करें (निर्देशित करने पर ही करें ) + वह फॉर्म आईडी डालें जिसे डाउनलोड करना है + ऐसा करने पर सभी डेटा स्थाईरुप से हट जायेगा (सर्वे प्रतिउत्तर , चित्र आदि ) I यह प्रतिक्रिया उलटनीय नहीं है आगे बढ़ने के लिए OK तथा समाप्त करने के लिए कैंसिल दबाएं I + ऐसा करने पर प्रतिउत्तर ,चित्र व ज़िप फाइल स्तापितरुप से हट जायेगा Iयह प्रतिक्रिया उलटनीय नहीं हैI आगे बढ़ने के लिए OK तथा समाप्त करने के लिए कैंसिल दबाएंI + कुछ उत्तर प्रस्तुत नहीं हुए I ऐसे उत्तरों को डिलीट करने से पूर्व बाहर भेजें I आगे बढ़ने के लिए OK तथा समाप्त करने के लिए कैंसिल दबाएं I + ऐसा करने पर सर्वे के उत्तर स्थाई रुप से डिलीट हो जायेंगे I आगे बढ़ने के लिए OK तथा समाप्त करने के लिए कैंसिल दबाएं I + अगला / आगामी + फार्म पूरा करने के बाद, सबमिट दबाके डेटा भेज दें .सबमिट करने के बाद , डेटा का अवलोकन किया जा सकता है, किन्तु उसे बदला नहीं जा सकता + सर्वे के उत्तरों को हटायें + क्या आप निशित तौर पे, सर्वे के उत्तरों को हटाना चाहते हैं. + प्राधिकरण आवशक है + कृपया अड्मिनिस्टर पासकोड डालें + प्राधिकरण अस्वीकृत + अमान्य पासकोड + दिए गए उत्तर को हटाएं + क्या आप इस प्रशन का उत्तर हटाना चाहते हैं? + डिवाइस आइडेंटिफायर + उपकरण अभिज्ञापक सेट करें + S.D कार्ड से सर्वे डेटा लोड हो रहा है + SD कार्ड से डेटा लोड पूर्ण हुआ + बूटस्ट्रैप ज़िप फाइल की प्रक्रिया में त्रुटि + त्रुटि : फॉर्म इस डैशबोर्ड का नहीं है + ट्रांसमिशन हिस्ट्री + डेटा के संचरण की पूर्ण जानकारी + प्रसार समाप्तः हुआ + उत्तर बदलना के लिए + SD कार्ड को जाँचिए + सभी उपभोक्ता सर्वे अन्य इकट्ठा करा डेटा डिलीट करें + स्थाईरुप से सभी सर्वे, उत्तर (चित्र तथा बाहर भेजदी गई ज़िप फाइल्स) और उपभोक्ता डिलीट करें + सर्वे + स्थाईरुप से सभी सर्वे उत्तर (चित्र और बाहर भेजी ज़िप फाइल सहित) हटाने के लिए + S D कार्ड की स्तिथि जाँचे + जाँचे की SD कार्ड ठीक तरह से लगा हुआ है तथा कार्ड पर कितनी खली जगह उपलब्ध है + SD कार्ड नमौजूद या लगा नहीं हैI सनुचित करें कि USB फाइल कॉपीइंग बंद कर दिया गया हैI + SD कार्ड सही तरह से लगा है + उपलब्ध स्थान (MB) + दिनांक चुनिए + कृपया बूटस्ट्रैप फाइल की प्रासेसिंग पूरा होने तक रुकें + डेटा आपके फ़ोन अथवा टेबलेट से सफलता पूर्ण हटा दिया गया है + उपकरण से डेटा हटाने में त्रुटि + सर्वे भाषाओं को समनुरूप या कॉन्फ़िगर करना + डेटा पॉइंट्स को सिंक करें + आर्डर बाय + डेटा पॉइंट्स सिंक हो रहे हैं + डेटा पॉइंट्स सिंकिंग समाप्त + %1$d डेटा पॉइंट्स सिंक हो चुके हैं + डेटा पॉइंट्स सिंकिंग असफल + नेटवर्क में त्रुटि + लोडिंग ... + पूर्व दिए उत्तर + पूर्व भरे फॉर्म, पिछले दिए गय उत्तरों के साथ ? + कृपया डैशबोर्ड में असाइनमेंट बनाये + + डेटा सिंक्रोनाइजेशन + डेटा अपलोड हो रहा है. कृपया प्रतीक्षा करें + सिंक हुआ फॉर्म: %1$d- असफल %2$d + सिंक हुआ फॉर्म: %1$d + + ऐप की भाषा + कृपया ऐप को पुनर्प्रारंभ करें + + आप के पास कोई सर्वे नियुक्त नहीं हैI फ्लो, FLOW डैशबोर्ड में सर्वे नियुक्त किये जाते हैंI + आपके पास कोई डेटा पॉइंट्स नहीं है I शुरू करने के लिए \"क्रिएट न्यू डेटा पॉइंट\" को चुनिए + + सर्वे + डेटा पॉइंट का चयन करें + + अधिक + + डेटा पॉइंट ढूंढने के लिए + + यह सर्वे को स्थाईरुप से हटाने के लिए. आगे बढ़ने के लिए OK तथा समाप्त करने के लिए कैंसिल करें + + कुछ उतारोें में त्रुटि है I जमा करने से पहले इन प्रश्नों का निरीक्षण करें I + यह उत्तर देना आवश्यक है + कृपया उत्तर दोहराएं + उत्तर मेल नहीं खा रहे हैं + चित्र लोड होने में त्रुटि + बाहरी स्त्रोत का उपयोग करें + खली फॉर्म जमा नहीं हो सकतेI कृपया पहले अनुकूल उत्तर दें I + कृपया चुनें + क्या आप इस संख्या को हटाना चाहेंगे ? + कोड टाइप करें + या + आकर लेने के लिए + आकार देखें + पॉइंट हटाने के लिए + क्या आप इस पॉइंट को हटाना चाहेंगे ? + फीचर डिलीट करें + क्या आप इस फीचर को हटाना चाहेंगे ? + + नया डेटा पॉइंट बनाइये + सर्वे स्टैट् + कुल डेटा पॉइंट्स + पिछले सात, 7, दिन + आज + दूर स्थान + डेटा पॉइंट + मानचित्र + तारीख + दुरी + स्टेटस + नाम + + बेनाम डेटा पॉइंट + पिछले संशोधित + पिछला उत्तर + कास्केड डाउनलोड नहीं हुआ है + फॉर्म्स + इतिहास + + कतार में है + प्रक्रिया जारी है + सिंक्ड + विफल + सुरक्षित + जमा हो चूका है + निर्यातित + + Akvo FLOW अपडेट + नया FLOW संस्करण उपलब्ध है. \"Update\" को चुनें व इसे डाउनलोड करें + अपडेट + अभी नहीं + नया FLOW संस्करण उपलब्ध है. क्या आप इसे स्तापित करना चाहेंगे ? + FLOW update दोव्न्लोडिंग त्रुटिपूर्ण + + समय व दिनांक + आपके फ़ोन पे दिखाया जा रहा समय सही नहीं है। उसे सही करें और पुन:प्रयास करें। आपका दिखाया गया समय व दिनांक यह है + समय अनुकूलन करें + + फॉर्म डाउनलोड जारी + फॉर्म सिंक करने में त्रुटि + फॉर्म असाइनमेंट्स FLOW dashboard पर पढ़े नहीं जा रहे हैं + यह फॉर्म %1$s FLOW dashboard पर पढ़ा नहीं जा सकता + सुनिशिचित करें की फॉर्म प्रकाशित हो और आपका स्थानिक समय व दिनांक सही हो + + एरिया /क्षेत्र + लाइन /रेखा + पॉइंट / बिंदु + स्तिथि/ स्थान ज्ञात नहीं + स्थिति /स्थान की जानकारी सही नहीं है + आकर सफल रुप से लिया गया है + पॉइंट जोड़े + क्या आप इस स्थान पे नया पॉइंट जोड़ना चाहतें हैं ? + + OK + रद्द + हाँ + नहीं + पुन: प्रयास + हटाएं + इस स्थान को खली नहीं छोड़ें + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 1a26c2d02..ccc4a1827 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,235 +1,233 @@ - + - - Akvo FLOW - Acc - Searching... - Ready - No Storage Space - Low Storage Space - The SD card on this device is full and you can no longer collect data. You must clear space on the SD card in order to continue. - You have %%% MB of storage space left on this device\'s SD card. Once the SD card is full, you will no longer be able to collect data. - Take Photo - Take Video - Check Geo Location - Lat - Lon - Height - Save - Clear - Submit - GPS must be enabled to do this. Select OK to go to\tthe settings page. Select Use GPS Satellites and then press the back button to return. Once GPS is enabled you can click Check Geo Location again to get your position. - Manage Users - Resize large images - Settings - Add User - No Users! - Display Name - Email Address - Select the current user by clicking. To create a new user, press the \'+\' icon. Long-click to edit a user. - Edit User - Delete User - Please click the Manage Users icon and choose a user before continuing. - Data has been exported to SD card - Preferences - Set default survey language and other preference data - Sync Data - Syncs survey data. You must have data access (mobile or wi-fi) to perform this operation. - Power Management - Toggles Wi-Fi to better manage battery life. - Keep Screen on During Survey Input - Prevents the screen from turning off while in survey entry mode. Be sure to manually shut the screen off (or exit the app) when not in use to avoid draining the battery. - About - Displays version information. - About - Akvo FLOW app - Akvo FLOW app\nDeveloped By: Akvo Foundation\nCopyright © 2010-2014 Stichting Akvo\nVersion - GPS Status - Launches the GPS Status application if you have it installed on this device. - GPS Status is not installed on this device. Please install it using the Google Play Store. - Please enter a description - View Help Text - View Help Video - View Help Photos - Help Options - Delete Response - Other... - Keep the last selected user logged-in between sessions - Sync data over mobile network - Survey Languages - Send Location Beacons - Scan Barcode - Scanner not installed. Please install the Barcode Scanner app from the Android Market. - Value is too small. Minimum value: - Value is too large. Maximum value: - Answer must be numeric - Data Server (only change if instructed) - Reload all surveys - Deletes and re-downloads assigned surveys (only run if instructed) - Please Confirm - Are you sure you want to purge and re-download surveys? - Survey submitted - Survey responses will be sent to the server. - Option - Languages - Invalid Selection - You must select at least one language - Please wait - Download Form - Downloads arbitrary surveys (only run if instructed) - Enter the form id you wish to download. - This will permanently delete all data (surveys, responses, images, and users) on the device. This operation is not reversible. Click OK to proceed or cancel to abort. - This will permanently delete responses, images, and zip files on the device. This operation is not reversible. Click OK to proceed or cancel to abort. - Some responses are not submitted. Make sure you export them before deletion. This operation is not reversible. Click OK to proceed or cancel to abort. - This will permanently delete this survey response. Click OK to proceed or cancel to abort. - Next - If you are finished with the form, press submit to send the data. Once you press submit, you can review the data but you will not be able to edit it. - Clear Survey Answers - Are you sure you want to clear your responses? - Authorization Required - Please enter the administrator passcode: - Authorization Failed - Invalid passcode - Clear Response - Do you want to clear the response to this question? - Device Identifier - Set device identifier - Loading survey data from SD card - Done loading data from SD card - Error processing bootstrap zip file - Error: Form doesn\'t belong to this FLOW instance - Transmission History - Transmission Started: - Transmission Finished: - Change response? - Do you want to change your response? - Delete Everything (users, surveys, and all collected data) - Permanently deletes all surveys, responses (including images and exported zip files) and users - Delete Collected Data and Images - Permanently deletes all survey responses (including images and exported zip files) - Check SD Card State - Checks to see that the SD card is mounted and displays the free space remaining on the card. - SD card missing or unmounted. Ensure that you have turned off \"USB File Copying\" - SD card is mounted properly. - Free Space (MB): - Select Date - Please wait until the bootstrap file has finished processing. - Data successfully deleted from the device - Error deleting data from the device - Configuring survey languages... - Sync Data Points - Order By - Syncing Data Points... - Data Points Sync finished - Synced %1$d Data Points - Data Points Sync failed - Network Error - Loading... - Pre-fill Responses - Pre-fill form with previous answers? - Assignment in Akvo FLOW dashboard required - - Data Synchronization - Uploading data. Please, wait... - Synced Forms: %1$d - Failed: %2$d - Synced Forms: %1$d - - App Language - Please, restart the app - - You have no surveys assigned. Surveys are assigned in the FLOW dashboard - You have no Data Points yet. Click on \'CREATE NEW DATA POINT\' to get started - - Surveys - Select Data Point - - More - - Search Data Point - - This will permanently delete this Survey. Click OK to proceed or cancel to abort. - - Some responses contain errors. You must review the following questions before you can submit: - This response is required - Please, repeat answer - Answers do not match - Can\'t load image preview - Use External Source - Empty forms cannot be submitted. Please, add the corresponding responses first. - please select - Do you want to delete this value? - type code - or - capture shape - view shape - Delete point - Do you want to remove this point? - Delete feature - Do you want to remove this feature? - - CREATE NEW DATA POINT - Survey Stats - Total Data Points: - Last 7 days: - Today: - Distance: - DATA POINTS - MAP - Date - Distance - Status - Name - - Unnamed data point - Last Modified: - Last Response: - Cascade files are not downloaded - FORMS - HISTORY - - Queued - In Progress - Synced - Failed - Saved - Submitted - Exported - - Akvo FLOW Update - A new version of the FLOW app is available. Click \'Update\' to download and install the new version. - Update - Not now - New FLOW version is downloaded. Do you want to install it now? - Error downloading FLOW update. - - Date and Time - Your phone time is inaccurate! Adjust your clock and try again. Your phone date and time is: - Adjust time - - Downloading forms - Error syncing forms - Form assignments could not be read from the FLOW dashboard. - Form %1$s could not be read from FLOW dashboard. - Ensure the form is published, and your local date/time settings are OK. - - Area - Line - Points - Location is not known - Location reading is not accurate enough - Shape captured successfully - Add Point - Do you want to add a new point at this location? - - OK - Cancel - Yes - No - Retry - Delete - This field cannot be blank - - fieldsurvey.ACTION_SURVEYS_SYNC - fieldsurvey.ACTION_LOCALES_SYNC - fieldsurvey.ACTION_DATA_SYNC - + Akvo FLOW + Acc + Searching... + Ready + No Storage Space + Low Storage Space + The SD card on this device is full and you can no longer collect data. You must clear space on the SD card in order to continue. + You have %%% MB of storage space left on this device\'s SD card. Once the SD card is full, you will no longer be able to collect data. + Take Photo + Take Video + Check Geo Location + Lat + Lon + Height + Save + Clear + Submit + GPS must be enabled to do this. Select OK to go to\tthe settings page. Select Use GPS Satellites and then press the back button to return. Once GPS is enabled you can click Check Geo Location again to get your position. + Manage Users + Resize large images + Settings + Add User + No Users! + Display Name + Email Address + Select the current user by clicking. To create a new user, press the \'+\' icon. Long-click to edit a user. + Edit User + Delete User + Please click the Manage Users icon and choose a user before continuing. + Data has been exported to SD card + Preferences + Set default survey language and other preference data + Sync Data + Syncs survey data. You must have data access (mobile or wi-fi) to perform this operation. + Power Management + Toggles Wi-Fi to better manage battery life. + Keep Screen on During Survey Input + Prevents the screen from turning off while in survey entry mode. Be sure to manually shut the screen off (or exit the app) when not in use to avoid draining the battery. + About + Displays version information. + About - Akvo FLOW app + Akvo FLOW app\nDeveloped By: Akvo Foundation\nCopyright © 2010-2014 Stichting Akvo\nVersion + GPS Status + Launches the GPS Status application if you have it installed on this device. + GPS Status is not installed on this device. Please install it using the Google Play Store. + Please enter a description + View Help Text + View Help Video + View Help Photos + Help Options + Delete Response + Other... + Keep the last selected user logged-in between sessions + Sync data over mobile network + Survey Languages + Send Location Beacons + Scan Barcode + Scanner not installed. Please install the Barcode Scanner app from the Android Market. + Value is too small. Minimum value: + Value is too large. Maximum value: + Answer must be numeric + Data Server (only change if instructed) + Reload all surveys + Deletes and re-downloads assigned surveys (only run if instructed) + Please Confirm + Are you sure you want to purge and re-download surveys? + Survey submitted + Survey responses will be sent to the server. + Option + Languages + Invalid Selection + You must select at least one language + Please wait + Download Form + Downloads arbitrary surveys (only run if instructed) + Enter the form id you wish to download. + This will permanently delete all data (surveys, responses, images, and users) on the device. This operation is not reversible. Click OK to proceed or cancel to abort. + This will permanently delete responses, images, and zip files on the device. This operation is not reversible. Click OK to proceed or cancel to abort. + Some responses are not submitted. Make sure you export them before deletion. This operation is not reversible. Click OK to proceed or cancel to abort. + This will permanently delete this survey response. Click OK to proceed or cancel to abort. + Next + If you are finished with the form, press submit to send the data. Once you press submit, you can review the data but you will not be able to edit it. + Clear Survey Answers + Are you sure you want to clear your responses? + Authorization Required + Please enter the administrator passcode: + Authorization Failed + Invalid passcode + Clear Response + Do you want to clear the response to this question? + Device Identifier + Set device identifier + Loading survey data from SD card + Done loading data from SD card + Error processing bootstrap zip file + Error: Form doesn\'t belong to this FLOW instance + Transmission History + Transmission Started: + Transmission Finished: + Change response? + Do you want to change your response? + Delete Everything (users, surveys, and all collected data) + Permanently deletes all surveys, responses (including images and exported zip files) and users + Delete Collected Data and Images + Permanently deletes all survey responses (including images and exported zip files) + Check SD Card State + Checks to see that the SD card is mounted and displays the free space remaining on the card. + SD card missing or unmounted. Ensure that you have turned off \"USB File Copying\" + SD card is mounted properly. + Free Space (MB): + Select Date + Please wait until the bootstrap file has finished processing. + Data successfully deleted from the device + Error deleting data from the device + Configuring survey languages... + Sync Data Points + Order By + Syncing Data Points... + Data Points Sync finished + Synced %1$d Data Points + Data Points Sync failed + Network Error + Loading... + Pre-fill Responses + Pre-fill form with previous answers? + Assignment in Akvo FLOW dashboard required + + Data Synchronization + Uploading data. Please, wait... + Synced Forms: %1$d - Failed: %2$d + Synced Forms: %1$d + + App Language + Please, restart the app + + You have no surveys assigned. Surveys are assigned in the FLOW dashboard + You have no Data Points yet. Click on \'CREATE NEW DATA POINT\' to get started + + Surveys + Select Data Point + + More + + Search Data Point + + This will permanently delete this Survey. Click OK to proceed or cancel to abort. + + Some responses contain errors. You must review the following questions before you can submit: + This response is required + Please, repeat answer + Answers do not match + Can\'t load image preview + Use External Source + Empty forms cannot be submitted. Please, add the corresponding responses first. + please select + Do you want to delete this value? + type code + or + capture shape + view shape + Delete point + Do you want to remove this point? + Delete feature + Do you want to remove this feature? + + CREATE NEW DATA POINT + Survey Stats + Total Data Points: + Last 7 days: + Today: + Distance: + DATA POINTS + MAP + Date + Distance + Status + Name + + Unnamed data point + Last Modified: + Last Response: + Cascade files are not downloaded + FORMS + HISTORY + + Queued + In Progress + Synced + Failed + Saved + Submitted + Exported + + Akvo FLOW Update + A new version of the FLOW app is available. Click \'Update\' to download and install the new version. + Update + Not now + New FLOW version is downloaded. Do you want to install it now? + Error downloading FLOW update. + + Date and Time + Your phone time is inaccurate! Adjust your clock and try again. Your phone date and time is: + Adjust time + + Downloading forms + Error syncing forms + Form assignments could not be read from the FLOW dashboard. + Form %1$s could not be read from FLOW dashboard. + Ensure the form is published, and your local date/time settings are OK. + + Area + Line + Points + Location is not known + Location reading is not accurate enough + Shape captured successfully + Add Point + Do you want to add a new point at this location? + + OK + Cancel + Yes + No + Retry + Delete + This field cannot be blank + + fieldsurvey.ACTION_SURVEYS_SYNC + fieldsurvey.ACTION_LOCALES_SYNC + fieldsurvey.ACTION_DATA_SYNC From b86db13f8e237ab0ceb31355a68f886e23fa07c3 Mon Sep 17 00:00:00 2001 From: Inigo Lopez de Heredia Date: Mon, 29 Jun 2015 12:05:32 +0100 Subject: [PATCH 14/20] [#314] Bump version number --- app/src/main/AndroidManifest.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 1ef9dddb7..914160a95 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -2,7 +2,7 @@ + android:versionName="2.1.8" > Date: Thu, 2 Jul 2015 14:51:47 +0100 Subject: [PATCH 15/20] [#304] Do not resend deleted forms' files * Add new status type * Ignore deleted forms in subsequent sync processes --- .../org/akvo/flow/dao/SurveyDbAdapter.java | 9 ++-- .../akvo/flow/service/DataSyncService.java | 44 ++++++++++--------- 2 files changed, 29 insertions(+), 24 deletions(-) diff --git a/app/src/main/java/org/akvo/flow/dao/SurveyDbAdapter.java b/app/src/main/java/org/akvo/flow/dao/SurveyDbAdapter.java index 9b9e4e6bd..8a94c6f52 100644 --- a/app/src/main/java/org/akvo/flow/dao/SurveyDbAdapter.java +++ b/app/src/main/java/org/akvo/flow/dao/SurveyDbAdapter.java @@ -178,10 +178,11 @@ public interface SurveyInstanceStatus { } public interface TransmissionStatus { - int QUEUED = 0; - int IN_PROGRESS = 1; - int SYNCED = 2; - int FAILED = 3; + int QUEUED = 0; + int IN_PROGRESS = 1; + int SYNCED = 2; + int FAILED = 3; + int FORM_DELETED = 4; } private static final String TAG = "SurveyDbAdapter"; diff --git a/app/src/main/java/org/akvo/flow/service/DataSyncService.java b/app/src/main/java/org/akvo/flow/service/DataSyncService.java index 79d965fc8..aa2ba1fda 100644 --- a/app/src/main/java/org/akvo/flow/service/DataSyncService.java +++ b/app/src/main/java/org/akvo/flow/service/DataSyncService.java @@ -122,6 +122,8 @@ public class DataSyncService extends IntentService { */ private static final int FILE_UPLOAD_RETRIES = 2; + private static final int ERROR_UNKNOWN = -1; + private PropertyUtil mProps; private SurveyDbAdapter mDatabase; @@ -459,16 +461,23 @@ private boolean syncFile(String filename, String formId, int status, String serv if (ok && action != null) { // If action is not null, notify GAE back-end that data is available // TODO: Do we need to send the checksum? - ok = sendProcessingNotification(serverBase, formId, action, destName); - } - - // Update database and display notification - // TODO: Ensure no Exception can be thrown from previous steps, to avoid leaking IN_PROGRESS status - if (ok) { - // Mark everything completed - mDatabase.updateTransmissionHistory(filename, TransmissionStatus.SYNCED); - } else { - mDatabase.updateTransmissionHistory(filename, TransmissionStatus.FAILED); + switch (sendProcessingNotification(serverBase, formId, action, destName)) { + case HttpStatus.SC_OK: + // Mark everything completed + mDatabase.updateTransmissionHistory(filename, TransmissionStatus.SYNCED); + break; + case HttpStatus.SC_NOT_FOUND: + // This form has been deleted in the dashboard, thus we cannot sync it + displayNotification(Integer.valueOf(formId), + "Form " + formId + " does not exist", "It has probably been deleted"); + mDatabase.updateTransmissionHistory(filename, TransmissionStatus.FORM_DELETED); + ok = false;// Consider this a failed transmission + break; + default:// Any error code + mDatabase.updateTransmissionHistory(filename, TransmissionStatus.FAILED); + ok = false;// Consider this a failed transmission + break; + } } return ok; @@ -587,25 +596,20 @@ private String getDeviceNotification(String serverBase) throws Exception { * Sends a message to the service with the file name that was just uploaded * so it can start processing the file */ - private boolean sendProcessingNotification(String serverBase, String formId, String action, String fileName) { - boolean success = false; + private int sendProcessingNotification(String serverBase, String formId, String action, String fileName) { String url = serverBase + NOTIFICATION_PATH + action + FORMID_PARAM + formId + FILENAME_PARAM + fileName + "&" + FlowApi.getDeviceParams(); try { HttpUtil.httpGet(url); - success = true; + return HttpStatus.SC_OK; } catch (HttpException e) { - if (e.getStatus() == HttpStatus.SC_NOT_FOUND) { - // This form has probably been deleted. - Log.e(TAG, "404 response for formId: " + formId); - displayNotification(Integer.valueOf(formId), - "Form " + formId + " does not exist", "It has probably been deleted"); - } + Log.e(TAG, e.getStatus() + " response for formId: " + formId); + return e.getStatus(); } catch (Exception e) { Log.e(TAG, "GAE sync notification failed for file: " + fileName); + return ERROR_UNKNOWN; } - return success; } private static String getDestName(String filename) { From 227ab4c0bcfa0b9caf1f6bcd091718e20c33fec0 Mon Sep 17 00:00:00 2001 From: Inigo Lopez de Heredia Date: Thu, 2 Jul 2015 16:37:49 +0100 Subject: [PATCH 16/20] [#304] Disable (grey out) deleted forms * Implement new UX and workflow for deleted forms notifications * See https://github.com/akvo/akvo-flow-mobile/issues/304 for more details --- app/src/main/AndroidManifest.xml | 5 --- .../flow/async/loader/SurveyInfoLoader.java | 6 ++- .../flow/broadcast/FormDeletedReceiver.java | 42 ------------------- .../org/akvo/flow/dao/SurveyDbAdapter.java | 15 +++---- .../akvo/flow/service/DataSyncService.java | 20 ++++----- .../flow/ui/fragment/SurveyListFragment.java | 7 +++- 6 files changed, 21 insertions(+), 74 deletions(-) delete mode 100644 app/src/main/java/org/akvo/flow/broadcast/FormDeletedReceiver.java diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index b6e1cbfde..914160a95 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -103,11 +103,6 @@ - - - diff --git a/app/src/main/java/org/akvo/flow/async/loader/SurveyInfoLoader.java b/app/src/main/java/org/akvo/flow/async/loader/SurveyInfoLoader.java index 7c4bbdb57..1c075bd89 100644 --- a/app/src/main/java/org/akvo/flow/async/loader/SurveyInfoLoader.java +++ b/app/src/main/java/org/akvo/flow/async/loader/SurveyInfoLoader.java @@ -54,7 +54,7 @@ protected Cursor loadData(SurveyDbAdapter database) { return database.query(table, SurveyQuery.PROJECTION, - SurveyColumns.DELETED + " <> 1 AND " + SurveyColumns.SURVEY_GROUP_ID + " = ?", + SurveyColumns.SURVEY_GROUP_ID + " = ?", new String[] { String.valueOf(mSurveyGroupId) }, Tables.SURVEY + "." + SurveyColumns.SURVEY_ID, null, @@ -66,12 +66,14 @@ public interface SurveyQuery { Tables.SURVEY + "." + SurveyColumns.SURVEY_ID, Tables.SURVEY + "." + SurveyColumns.NAME, Tables.SURVEY + "." + SurveyColumns.VERSION, + Tables.SURVEY + "." + SurveyColumns.DELETED, Tables.SURVEY_INSTANCE + "." + SurveyInstanceColumns.SUBMITTED_DATE }; int SURVEY_ID = 0; int NAME = 1; int VERSION = 2; - int SUBMITTED = 3; + int DELETED = 3; + int SUBMITTED = 4; } } diff --git a/app/src/main/java/org/akvo/flow/broadcast/FormDeletedReceiver.java b/app/src/main/java/org/akvo/flow/broadcast/FormDeletedReceiver.java deleted file mode 100644 index 2fe5689fb..000000000 --- a/app/src/main/java/org/akvo/flow/broadcast/FormDeletedReceiver.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (C) 2015 Stichting Akvo (Akvo Foundation) - * - * This file is part of Akvo FLOW. - * - * Akvo FLOW is free software: you can redistribute it and modify it under the terms of - * the GNU Affero General Public License (AGPL) as published by the Free Software Foundation, - * either version 3 of the License or any later version. - * - * Akvo FLOW 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 Affero General Public License included below for more details. - * - * The full license text can also be seen at . - */ - -package org.akvo.flow.broadcast; - -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.widget.Toast; - -import org.akvo.flow.dao.SurveyDbAdapter; -import org.akvo.flow.service.DataSyncService; -import org.akvo.flow.service.SurveyDownloadService; - -public class FormDeletedReceiver extends BroadcastReceiver { - public static final String FORM_ID = "formId"; - - public void onReceive(Context context, Intent intent) { - String formId = intent.getStringExtra(FORM_ID); - - SurveyDbAdapter db = new SurveyDbAdapter(context).open(); - db.deleteSurvey(formId, false); - db.close(); - - Toast.makeText(context, "Form " + formId + " has been removed", Toast.LENGTH_SHORT) - .show(); - SurveyDownloadService.sendBroadcastNotification(context); - } -} diff --git a/app/src/main/java/org/akvo/flow/dao/SurveyDbAdapter.java b/app/src/main/java/org/akvo/flow/dao/SurveyDbAdapter.java index 8a94c6f52..8b7ab5a5d 100644 --- a/app/src/main/java/org/akvo/flow/dao/SurveyDbAdapter.java +++ b/app/src/main/java/org/akvo/flow/dao/SurveyDbAdapter.java @@ -1365,16 +1365,11 @@ public void deleteSurveyGroup(long surveyGroupId) { * * @param surveyId */ - public void deleteSurvey(String surveyId, boolean physicalDelete) { - if (!physicalDelete) { - ContentValues updatedValues = new ContentValues(); - updatedValues.put(SurveyColumns.DELETED, 1); - database.update(Tables.SURVEY, updatedValues, SurveyColumns.SURVEY_ID + " = ?", - new String[] { surveyId }); - } else { - database.delete(Tables.SURVEY, SurveyColumns.SURVEY_ID + " = ? ", - new String[] { surveyId }); - } + public void deleteSurvey(String surveyId) { + ContentValues updatedValues = new ContentValues(); + updatedValues.put(SurveyColumns.DELETED, 1); + database.update(Tables.SURVEY, updatedValues, SurveyColumns.SURVEY_ID + " = ?", + new String[] { surveyId }); } /** diff --git a/app/src/main/java/org/akvo/flow/service/DataSyncService.java b/app/src/main/java/org/akvo/flow/service/DataSyncService.java index aa2ba1fda..369c7b30a 100644 --- a/app/src/main/java/org/akvo/flow/service/DataSyncService.java +++ b/app/src/main/java/org/akvo/flow/service/DataSyncService.java @@ -33,7 +33,6 @@ import org.akvo.flow.api.S3Api; import org.akvo.flow.api.response.FormInstance; import org.akvo.flow.api.response.Response; -import org.akvo.flow.broadcast.FormDeletedReceiver; import org.akvo.flow.dao.SurveyDbAdapter; import org.akvo.flow.dao.SurveyDbAdapter.ResponseColumns; import org.akvo.flow.dao.SurveyDbAdapter.SurveyInstanceColumns; @@ -390,8 +389,9 @@ private String cleanVal(String val) { */ private void syncFiles() { final String serverBase = StatusUtil.getServerBase(this); - // Sync missing files. This will update the status of the transmissions if necessary - checkMissingFiles(serverBase); + // Check notifications for this device. This will update the status of the transmissions + // if necessary, or mark form as deleted. + checkDeviceNotifications(serverBase); List transmissions = mDatabase.getUnsyncedTransmissions(); @@ -522,7 +522,7 @@ private boolean sendFile(String fileAbsolutePath, String dir, String contentType * 1- Request the list of files to the server * 2- Update the status of those files in the local database */ - private void checkMissingFiles(String serverBase) { + private void checkDeviceNotifications(String serverBase) { try { String response = getDeviceNotification(serverBase); if (!TextUtils.isEmpty(response)) { @@ -542,7 +542,9 @@ private void checkMissingFiles(String serverBase) { JSONArray jForms = jResponse.optJSONArray("deletedForms"); if (jForms != null) { for (int i=0; i loader, Cursor cursor) { s.mId = cursor.getString(SurveyQuery.SURVEY_ID); s.mName = cursor.getString(SurveyQuery.NAME); s.mVersion = String.valueOf(cursor.getFloat(SurveyQuery.VERSION)); + s.mDeleted = cursor.getInt(SurveyQuery.DELETED) == 1; if (!cursor.isNull(SurveyQuery.SUBMITTED)) { s.mLastSubmission = cursor.getLong(SurveyQuery.SUBMITTED); mRegistered = true; @@ -269,6 +271,7 @@ class SurveyInfo { String mName; String mVersion; Long mLastSubmission; + boolean mDeleted; } } From b3b58a220dec3069abe654c7ece3466696146a18 Mon Sep 17 00:00:00 2001 From: Inigo Lopez de Heredia Date: Thu, 2 Jul 2015 17:00:03 +0100 Subject: [PATCH 17/20] [#304] Add form deletion warning * Be nice and keep the user updated! --- .../java/org/akvo/flow/ui/fragment/SurveyListFragment.java | 7 ++++++- app/src/main/res/values/strings.xml | 1 + 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/org/akvo/flow/ui/fragment/SurveyListFragment.java b/app/src/main/java/org/akvo/flow/ui/fragment/SurveyListFragment.java index 7bab6cc09..65e19822e 100644 --- a/app/src/main/java/org/akvo/flow/ui/fragment/SurveyListFragment.java +++ b/app/src/main/java/org/akvo/flow/ui/fragment/SurveyListFragment.java @@ -183,7 +183,12 @@ public View getView(int position, View convertView, ViewGroup parent) { surveyNameView.setText(surveyInfo.mName); surveyVersionView.setText("v" + surveyInfo.mVersion); - final boolean enabled = !surveyInfo.mDeleted && isEnabled(surveyInfo.mId); + boolean enabled = isEnabled(surveyInfo.mId); + if (surveyInfo.mDeleted) { + enabled = false; + surveyVersionView.append(" - " + getString(R.string.form_deleted)); + } + listItem.setEnabled(enabled); surveyNameView.setEnabled(enabled); surveyVersionView.setEnabled(enabled); diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index ccc4a1827..490ae8f0a 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -209,6 +209,7 @@ Form assignments could not be read from the FLOW dashboard. Form %1$s could not be read from FLOW dashboard. Ensure the form is published, and your local date/time settings are OK. + This form has been deleted. Area Line From 427f0c2cdd4fc99c2463edc237d093c70cc1ee0e Mon Sep 17 00:00:00 2001 From: Inigo Lopez de Heredia Date: Mon, 6 Jul 2015 14:18:29 +0100 Subject: [PATCH 18/20] [#304, #319] Remove DB.close() duplicate statement * DB will be closed in `finally` block --- app/src/main/java/org/akvo/flow/service/DataSyncService.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/src/main/java/org/akvo/flow/service/DataSyncService.java b/app/src/main/java/org/akvo/flow/service/DataSyncService.java index 369c7b30a..950ce6012 100644 --- a/app/src/main/java/org/akvo/flow/service/DataSyncService.java +++ b/app/src/main/java/org/akvo/flow/service/DataSyncService.java @@ -142,8 +142,6 @@ protected void onHandleIntent(Intent intent) { if (StatusUtil.hasDataConnection(this)) { syncFiles();// Sync everything } - - mDatabase.close(); } catch (Exception e) { Log.e(TAG, e.getMessage()); PersistentUncaughtExceptionHandler.recordException(e); From c738b818d85d6c926c4a206349e9b5c0f47259d5 Mon Sep 17 00:00:00 2001 From: Inigo Lopez de Heredia Date: Mon, 6 Jul 2015 16:43:16 +0100 Subject: [PATCH 19/20] [#304] Reuse 'data exported' notification --- .../main/java/org/akvo/flow/service/DataSyncService.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/org/akvo/flow/service/DataSyncService.java b/app/src/main/java/org/akvo/flow/service/DataSyncService.java index 950ce6012..70dc31881 100644 --- a/app/src/main/java/org/akvo/flow/service/DataSyncService.java +++ b/app/src/main/java/org/akvo/flow/service/DataSyncService.java @@ -116,6 +116,9 @@ public class DataSyncService extends IntentService { private static final String ACTION_SUBMIT = "submit"; private static final String ACTION_IMAGE = "image"; + // Reuse notification for data export + private static final int NOTIFICATION_DATA_EXPORT = 1; + /** * Number of retries to upload a file to S3 */ @@ -164,7 +167,8 @@ private void exportSurveys() { for (long id : getUnexportedSurveys()) { ZipFileData zipFileData = formZip(id); if (zipFileData != null) { - displayNotification(id, getString(R.string.exportcomplete), getDestName(zipFileData.filename)); + displayNotification(NOTIFICATION_DATA_EXPORT, getString(R.string.exportcomplete), + getDestName(zipFileData.filename)); // Create new entries in the transmission queue mDatabase.createTransmission(id, zipFileData.formId, zipFileData.filename); @@ -466,7 +470,7 @@ private boolean syncFile(String filename, String formId, int status, String serv break; case HttpStatus.SC_NOT_FOUND: // This form has been deleted in the dashboard, thus we cannot sync it - displayNotification(Integer.valueOf(formId), + displayNotification(formId(formId), "Form " + formId + " does not exist", "It has probably been deleted"); mDatabase.updateTransmissionHistory(filename, TransmissionStatus.FORM_DELETED); ok = false;// Consider this a failed transmission From 01df2c733b837b6eccdfe512c4a709821baa6a56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?I=C3=B1igo?= Date: Wed, 8 Jul 2015 09:24:55 +0100 Subject: [PATCH 20/20] [#314] Update release notes --- RELEASE_NOTES.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 35b09263d..38cfc9fca 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,6 +1,20 @@ Akvo FLOW app release notes =========================== +# ver 2.1.8 +Date: 8 July 2014 + +New and noteworthy +------------------ +* Forms deleted in the dashboard are disabled in the app [#304] +* Hindi language can be selected for the app UI [#312] +* Multiple barcode questions can be answered with an external bluetooth scanner[#299] + +Resolved issues +--------------- +* Submitted questions are not editable [#307] +* Handle registration form updates (monitoring surveys)[#310] + # ver 2.1.7 Date: 10 Jun 2015