diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 6981f6618..1d7bd9027 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,6 +1,20 @@ Akvo FLOW app release notes =========================== +#ver 2.1.2 +Date: 20 February 2015 + +New and noteworthy +------------------ +* Synchronised datapoints (monitoring surveys) can now get the media responses downloaded (images and videos) [#96] + +Resolved issues +--------------- +* Lack of GPS results no longer in an app crash [#262] +* Option values trailing spaces are now handled correctly. This caused some dependent questions to not be displayed [#261] +* Manual entries in geoshape features display a confirmation dialog [#236] +* Add `androidId` unique identifier to device attributes [#259] + #ver 2.1.1 Date: 28 January 2015 diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 62480a15d..1b7da4d58 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -2,7 +2,7 @@ + android:versionName="2.1.2" > getSurveyedLocales(long surveyGroup, String timestamp) throws IOException, HttpException { - final String query = PARAM.IMEI + IMEI - + "&" + PARAM.LAST_UPDATED + (!TextUtils.isEmpty(timestamp)? timestamp : "0") - + "&" + PARAM.PHONE_NUMBER + PHONE_NUMBER - + "&" + PARAM.SURVEY_GROUP + surveyGroup - + "&" + PARAM.TIMESTAMP + getTimestamp(); - + final String query = Param.IMEI + IMEI + + "&" + Param.LAST_UPDATED + (!TextUtils.isEmpty(timestamp)? timestamp : "0") + + "&" + Param.PHONE_NUMBER + PHONE_NUMBER + + "&" + Param.SURVEY_GROUP + surveyGroup + + "&" + Param.TIMESTAMP + getTimestamp(); + final String url = BASE_URL + Path.SURVEYED_LOCALE + "?" + query //+ "&" + PARAM.HMAC + URLEncoder.encode(getAuthorization(query), "UTF-8"); - + "&" + PARAM.HMAC + getAuthorization(query); + + "&" + Param.HMAC + getAuthorization(query); String response = HttpUtil.httpGet(url); if (response != null) { SurveyedLocalesResponse slRes = new SurveyedLocaleParser().parseResponse(response); @@ -86,32 +87,18 @@ public List getSurveyedLocales(long surveyGroup, String timestam return null; } - private static String getPhoneNumber(Context context) { - try { - String phoneNumber = StatusUtil.getPhoneNumber(context); - if (phoneNumber != null) { - return URLEncoder.encode(phoneNumber, "UTF-8"); - } + private static String URLEncode(String param) { + if (TextUtils.isEmpty(param)) { return ""; - } catch (UnsupportedEncodingException e) { - Log.e(TAG, e.getMessage()); - return null; } - } - - private static String getImei(Context context) { try { - String imei = StatusUtil.getImei(context); - if (imei != null) { - return URLEncoder.encode(StatusUtil.getImei(context), "UTF-8"); - } - return ""; + return URLEncoder.encode(param, "UTF-8"); } catch (UnsupportedEncodingException e) { Log.e(TAG, e.getMessage()); - return null; + return ""; } } - + private static String getApiKey(Context context) { PropertyUtil props = new PropertyUtil(context.getResources()); return props.getProperty(ConstantUtil.API_KEY); @@ -128,9 +115,7 @@ private String getAuthorization(String query) { byte[] rawHmac = mac.doFinal(query.getBytes()); authorization = Base64.encodeToString(rawHmac, Base64.DEFAULT); - } catch (NoSuchAlgorithmException e) { - Log.e(TAG, e.getMessage()); - } catch (InvalidKeyException e) { + } catch (NoSuchAlgorithmException | InvalidKeyException e) { Log.e(TAG, e.getMessage()); } @@ -149,17 +134,29 @@ private String getTimestamp() { return null; } } + + public static String getDeviceParams() { + Context context = FlowApp.getApp(); + return Param.PHONE_NUMBER + URLEncode(StatusUtil.getPhoneNumber(context)) + + "&" + Param.ANDROID_ID + URLEncode(PlatformUtil.getAndroidID(context)) + + "&" + Param.IMEI + URLEncode(StatusUtil.getImei(context)) + + "&" + Param.VERSION + URLEncode(PlatformUtil.getVersionName(context)) + + "&" + Param.DEVICE_ID + URLEncode(StatusUtil.getDeviceId(context)); + } interface Path { String SURVEYED_LOCALE = "/surveyedlocale"; } - interface PARAM { + interface Param { String SURVEY_GROUP = "surveyGroupId="; String PHONE_NUMBER = "phoneNumber="; String IMEI = "imei="; String TIMESTAMP = "ts="; String LAST_UPDATED = "lastUpdateTime="; String HMAC = "h="; + String VERSION = "ver="; + String DEVICE_ID = "devId="; + String ANDROID_ID = "androidId="; } } diff --git a/app/src/main/java/org/akvo/flow/async/MediaSyncTask.java b/app/src/main/java/org/akvo/flow/async/MediaSyncTask.java new file mode 100644 index 000000000..d3b33c6de --- /dev/null +++ b/app/src/main/java/org/akvo/flow/async/MediaSyncTask.java @@ -0,0 +1,83 @@ +/* + * 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.async; + +import android.content.Context; +import android.os.AsyncTask; +import android.util.Log; + +import org.akvo.flow.api.S3Api; +import org.akvo.flow.util.ConstantUtil; +import org.akvo.flow.util.StatusUtil; + +import java.io.File; +import java.io.IOException; +import java.lang.ref.WeakReference; + +/** + * Download media files (images, videos) from synced forms. + */ +public class MediaSyncTask extends AsyncTask { + private static final String TAG = MediaSyncTask.class.getSimpleName(); + + public interface DownloadListener { + public void onResourceDownload(boolean done); + } + + private WeakReference mListener;// Use a WeakReferences to avoid memory leaks + private Context mContext; + private File mFile; + + /** + * Download a media file. Provided file must be already updated to use the local filesystem path. + */ + public MediaSyncTask(Context context, File file, DownloadListener listener) { + mContext = context.getApplicationContext(); + mListener = new WeakReference<>(listener); + mFile = file; + } + + @Override + protected Boolean doInBackground(Void... params) { + if (!StatusUtil.hasDataConnection(mContext)) { + Log.d(TAG, "No internet connection. Can't perform the requested operation"); + return false; + } + + try { + // Download resource and return success status + S3Api s3 = new S3Api(mContext); + s3.get(ConstantUtil.S3_IMAGE_DIR + mFile.getName(), mFile); + return true; + } catch (IOException e) { + Log.e(TAG, e.getMessage()); + if (mFile.exists()) { + mFile.delete(); + } + } + return false; + } + + @Override + protected void onPostExecute(Boolean result) { + final DownloadListener listener = mListener.get(); + if (listener != null) { + listener.onResourceDownload(Boolean.TRUE.equals(result)); + } + } + +} diff --git a/app/src/main/java/org/akvo/flow/domain/Dependency.java b/app/src/main/java/org/akvo/flow/domain/Dependency.java index c5973526e..e29567df8 100644 --- a/app/src/main/java/org/akvo/flow/domain/Dependency.java +++ b/app/src/main/java/org/akvo/flow/domain/Dependency.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. * @@ -52,7 +52,7 @@ public boolean isMatch(String val) { for (String v : val.split("\\|", -1)) { for (String a : answer.split("\\|", -1)) { - if (v.equals(a)) { + if (v.trim().equals(a.trim())) { return true; } } 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 3fd8a6345..6d3c28327 100644 --- a/app/src/main/java/org/akvo/flow/service/DataSyncService.java +++ b/app/src/main/java/org/akvo/flow/service/DataSyncService.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010-2014 Stichting Akvo (Akvo Foundation) + * Copyright (C) 2010-2015 Stichting Akvo (Akvo Foundation) * * This file is part of Akvo FLOW. * @@ -27,6 +27,7 @@ import android.util.Log; import org.akvo.flow.R; +import org.akvo.flow.api.FlowApi; import org.akvo.flow.api.S3Api; import org.akvo.flow.dao.SurveyDbAdapter; import org.akvo.flow.dao.SurveyDbAdapter.ResponseColumns; @@ -41,7 +42,6 @@ import org.akvo.flow.util.FileUtil; import org.akvo.flow.util.FileUtil.FileType; import org.akvo.flow.util.HttpUtil; -import org.akvo.flow.util.PlatformUtil; import org.akvo.flow.util.PropertyUtil; import org.akvo.flow.util.StatusUtil; import org.akvo.flow.util.StringUtil; @@ -54,7 +54,6 @@ import java.io.File; import java.io.FileOutputStream; import java.io.IOException; -import java.net.URLEncoder; import java.security.InvalidKeyException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; @@ -99,14 +98,9 @@ public class DataSyncService extends IntentService { private static final String SIG_FILE_NAME = ".sig"; // Sync constants - private static final String DEVICE_NOTIFICATION_PATH = "/devicenotification?"; + 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 NOTIFICATION_PN_PARAM = "&phoneNumber="; - private static final String CHECKSUM_PARAM = "&checksum="; - private static final String IMEI_PARAM = "&imei="; - private static final String DEVICE_ID_PARAM = "&devId="; - private static final String VERSION_PARAM = "&ver="; private static final String DATA_CONTENT_TYPE = "application/zip"; private static final String IMAGE_CONTENT_TYPE = "image/jpeg"; @@ -115,8 +109,6 @@ public class DataSyncService extends IntentService { private static final String ACTION_SUBMIT = "submit"; private static final String ACTION_IMAGE = "image"; - private static final String UTF8 = "UTF-8"; - /** * Number of retries to upload a file to S3 */ @@ -476,7 +468,8 @@ 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 - ok = sendProcessingNotification(serverBase, action, destName, null);// TODO: checksum + // TODO: Do we need to send the checksum? + ok = sendProcessingNotification(serverBase, action, destName); } // Update database and display notification @@ -587,22 +580,7 @@ private void setFileTransmissionFailed(String filename) { * @throws Exception */ private String getDeviceNotification(String serverBase) throws Exception { - String phoneNumber = StatusUtil.getPhoneNumber(this); - String imei = StatusUtil.getImei(this); - String deviceId = mDatabase.getPreference(ConstantUtil.DEVICE_IDENT_KEY); - String version = PlatformUtil.getVersionName(this); - - String url = serverBase + DEVICE_NOTIFICATION_PATH - + (phoneNumber != null ? - NOTIFICATION_PN_PARAM.substring(1) + URLEncoder.encode(phoneNumber, UTF8) - : "") - + (imei != null ? - IMEI_PARAM + URLEncoder.encode(imei, UTF8) - : "") - + (deviceId != null ? - DEVICE_ID_PARAM + URLEncoder.encode(deviceId, UTF8) - : "") - + VERSION_PARAM + URLEncoder.encode(version, UTF8); + String url = serverBase + DEVICE_NOTIFICATION_PATH + "?" + FlowApi.getDeviceParams(); return HttpUtil.httpGet(url); } @@ -613,14 +591,10 @@ private String getDeviceNotification(String serverBase) throws Exception { * @param fileName * @return */ - private boolean sendProcessingNotification(String serverBase, String action, String fileName, - String checksum) { + private boolean sendProcessingNotification(String serverBase, String action, String fileName) { boolean success = false; String url = serverBase + NOTIFICATION_PATH + action - + FILENAME_PARAM + fileName - + NOTIFICATION_PN_PARAM + StatusUtil.getPhoneNumber(this) - + IMEI_PARAM + StatusUtil.getImei(this) - + (checksum != null ? CHECKSUM_PARAM + checksum : ""); + + FILENAME_PARAM + fileName + "&" + FlowApi.getDeviceParams(); try { HttpUtil.httpGet(url); success = true; diff --git a/app/src/main/java/org/akvo/flow/service/ExceptionReportingService.java b/app/src/main/java/org/akvo/flow/service/ExceptionReportingService.java index 2169989bc..3248043a5 100644 --- a/app/src/main/java/org/akvo/flow/service/ExceptionReportingService.java +++ b/app/src/main/java/org/akvo/flow/service/ExceptionReportingService.java @@ -59,6 +59,7 @@ public class ExceptionReportingService extends Service { private static final String DEV_ID_PARAM = "deviceIdentifier"; private static final String DATE_PARAM = "date"; private static final String TRACE_PARAM = "trace"; + private static final String ANDROID_ID_PARAM = "androidId"; private static final long INITIAL_DELAY = 60000; private static final long INTERVAL = 300000; @@ -153,6 +154,8 @@ public void submitStackTraces(String server) { File f = new File(dir, list[i]); String trace = FileUtil.readFileAsString(f); + // We cannot use the standard FlowApi.getDeviceParams, fot this service uses + // a different naming convention... Map params = new HashMap(); params.put(ACTION_PARAM, ACTION_VALUE); params.put(PHONE_PARAM, phoneNumber); @@ -162,6 +165,7 @@ public void submitStackTraces(String server) { DATE_FMT.get().format(new Date(f.lastModified()))); params.put(DEV_ID_PARAM, deviceId); params.put(TRACE_PARAM, trace); + params.put(ANDROID_ID_PARAM, PlatformUtil.getAndroidID(this)); String response = HttpUtil.httpPost(server + EXCEPTION_SERVICE_PATH, params); if (response == null diff --git a/app/src/main/java/org/akvo/flow/service/LocationService.java b/app/src/main/java/org/akvo/flow/service/LocationService.java index 6a2a2cd91..452076968 100644 --- a/app/src/main/java/org/akvo/flow/service/LocationService.java +++ b/app/src/main/java/org/akvo/flow/service/LocationService.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. * @@ -28,11 +28,11 @@ import android.os.IBinder; import android.util.Log; +import org.akvo.flow.api.FlowApi; import org.akvo.flow.dao.SurveyDbAdapter; import org.akvo.flow.exception.PersistentUncaughtExceptionHandler; import org.akvo.flow.util.ConstantUtil; import org.akvo.flow.util.HttpUtil; -import org.akvo.flow.util.PlatformUtil; import org.akvo.flow.util.StatusUtil; /** @@ -48,17 +48,12 @@ public class LocationService extends Service { private static final long INITIAL_DELAY = 60000; private static final long INTERVAL = 1800000; private static boolean sendBeacon = true; - private static final String BEACON_SERVICE_PATH = "/locationBeacon?action=beacon&phoneNumber="; - private static final String IMEI = "&imei="; - private static final String VER = "&ver="; + private static final String BEACON_SERVICE_PATH = "/locationBeacon?action=beacon"; private static final String LAT = "&lat="; private static final String LON = "&lon="; private static final String ACC = "&acc="; - private static final String DEV_ID = "&devId="; private static final String OS_VERSION = "&osVersion="; private static final String TAG = "LocationService"; - private String version; - private String deviceId; public IBinder onBind(Intent intent) { return null; @@ -79,13 +74,10 @@ public int onStartCommand(final Intent intent, int flags, int startid) { database = new SurveyDbAdapter(this); database.open(); - String val = database - .getPreference(ConstantUtil.LOCATION_BEACON_SETTING_KEY); - deviceId = database.getPreference(ConstantUtil.DEVICE_IDENT_KEY); + String val = database.getPreference(ConstantUtil.LOCATION_BEACON_SETTING_KEY); if (val != null) { sendBeacon = Boolean.parseBoolean(val); } - version = PlatformUtil.getVersionName(this); } finally { if (database != null) { database.close(); @@ -128,23 +120,12 @@ public void onCreate() { private void sendLocation(String serverBase, Location loc) { if (serverBase != null) { try { - String phoneNumber = StatusUtil.getPhoneNumber(this); - if (phoneNumber != null) { - String url = serverBase - + BEACON_SERVICE_PATH + URLEncoder.encode(phoneNumber, "UTF-8") - + IMEI + URLEncoder.encode(StatusUtil.getImei(this), "UTF-8"); - if (loc != null) { - url += LAT + loc.getLatitude() + LON + loc.getLongitude() - + ACC + loc.getAccuracy(); - } - url += VER + version; - url += OS_VERSION - + URLEncoder.encode("Android " + android.os.Build.VERSION.RELEASE); - if (deviceId != null) { - url += DEV_ID + URLEncoder.encode(deviceId, "UTF-8"); - } - HttpUtil.httpGet(url); + String url = serverBase + BEACON_SERVICE_PATH + "&" + FlowApi.getDeviceParams(); + if (loc != null) { + url += LAT + loc.getLatitude() + LON + loc.getLongitude() + ACC + loc.getAccuracy(); } + url += OS_VERSION + URLEncoder.encode("Android " + android.os.Build.VERSION.RELEASE); + HttpUtil.httpGet(url); } catch (Exception e) { Log.e(TAG, "Could not send location beacon", e); PersistentUncaughtExceptionHandler.recordException(e); 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 68dc4c3af..522c57803 100644 --- a/app/src/main/java/org/akvo/flow/service/SurveyDownloadService.java +++ b/app/src/main/java/org/akvo/flow/service/SurveyDownloadService.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010-2014 Stichting Akvo (Akvo Foundation) + * Copyright (C) 2010-2015 Stichting Akvo (Akvo Foundation) * * This file is part of Akvo FLOW. * @@ -21,7 +21,6 @@ import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; -import java.net.URLEncoder; import java.util.ArrayList; import java.util.HashSet; import java.util.List; @@ -38,6 +37,7 @@ import android.util.Log; import org.akvo.flow.R; +import org.akvo.flow.api.FlowApi; import org.akvo.flow.api.S3Api; import org.akvo.flow.api.parser.csv.SurveyMetaParser; import org.akvo.flow.dao.SurveyDao; @@ -54,7 +54,6 @@ import org.akvo.flow.util.FileUtil.FileType; import org.akvo.flow.util.HttpUtil; import org.akvo.flow.util.LangsPreferenceUtil; -import org.akvo.flow.util.PlatformUtil; import org.akvo.flow.util.StatusUtil; import org.akvo.flow.util.ViewUtil; @@ -70,12 +69,8 @@ public class SurveyDownloadService extends IntentService { private static final String DEFAULT_TYPE = "Survey"; - private static final String SURVEY_LIST_SERVICE_PATH = "/surveymanager?action=getAvailableSurveysDevice&devicePhoneNumber="; + private static final String SURVEY_LIST_SERVICE_PATH = "/surveymanager?action=getAvailableSurveysDevice"; private static final String SURVEY_HEADER_SERVICE_PATH = "/surveymanager?action=getSurveyHeader&surveyId="; - private static final String DEV_ID_PARAM = "&devId="; - private static final String IMEI_PARAM = "&imei="; - private static final String VERSION_PARAM = "&ver="; - private static final String NUMBER_PARAM = "&devicePhoneNumber="; private SurveyDbAdapter databaseAdaptor; @@ -116,13 +111,12 @@ public void onCreate() { private void checkAndDownload(String[] surveyIds) throws IOException { // Load preferences final String serverBase = StatusUtil.getServerBase(this); - final String deviceId = getDeviceId(); List surveys; if (surveyIds != null) { - surveys = getSurveyHeaders(serverBase, surveyIds, deviceId); + surveys = getSurveyHeaders(serverBase, surveyIds); } else { - surveys = checkForSurveys(serverBase, deviceId); + surveys = checkForSurveys(serverBase); } // if there are surveys for this device, see if we need them @@ -162,10 +156,6 @@ private void checkAndDownload(String[] surveyIds) throws IOException { } } - private String getDeviceId() { - return databaseAdaptor.getPreference(ConstantUtil.DEVICE_IDENT_KEY); - } - /** * Downloads the survey based on the ID and then updates the survey object * with the filename and location @@ -287,13 +277,11 @@ private void downloadResources(final String sid, final Set resources) { /** * invokes a service call to get the header information for multiple surveys */ - private List getSurveyHeaders(String serverBase, String[] surveyIds, String deviceId) { + private List getSurveyHeaders(String serverBase, String[] surveyIds) { List surveys = new ArrayList(); for (String id : surveyIds) { try { - final String url = serverBase + SURVEY_HEADER_SERVICE_PATH + id - + NUMBER_PARAM + StatusUtil.getPhoneNumber(this) - + (deviceId != null ? DEV_ID_PARAM + URLEncoder.encode(deviceId, "UTF-8") : ""); + final String url = serverBase + SURVEY_HEADER_SERVICE_PATH + id + "&" + FlowApi.getDeviceParams(); String response = HttpUtil.httpGet(url); if (response != null) { surveys.addAll(new SurveyMetaParser().parseList(response, true)); @@ -315,20 +303,10 @@ private List getSurveyHeaders(String serverBase, String[] surveyIds, Str * @return - an arrayList of Survey objects with the id and version populated * TODO: Move this feature to FLOWApi */ - private List checkForSurveys(String serverBase, String deviceId) { + private List checkForSurveys(String serverBase) { List surveys = new ArrayList(); - String phoneNumber = StatusUtil.getPhoneNumber(this); - if (phoneNumber == null) { - phoneNumber = ""; - } - String imei = StatusUtil.getImei(this); - String version = PlatformUtil.getVersionName(this); try { - final String url = serverBase - + SURVEY_LIST_SERVICE_PATH + URLEncoder.encode(phoneNumber, "UTF-8") - + IMEI_PARAM + URLEncoder.encode(imei, "UTF-8") - + VERSION_PARAM + URLEncoder.encode(version, "UTF-8") - + (deviceId != null ? DEV_ID_PARAM + URLEncoder.encode(deviceId, "UTF-8") : ""); + final String url = serverBase + SURVEY_LIST_SERVICE_PATH + "&" + FlowApi.getDeviceParams(); String response = HttpUtil.httpGet(url); if (response != null) { surveys = new SurveyMetaParser().parseList(response); diff --git a/app/src/main/java/org/akvo/flow/ui/fragment/SurveyedLocaleListFragment.java b/app/src/main/java/org/akvo/flow/ui/fragment/SurveyedLocaleListFragment.java index d65bfe42c..b9e7b7cfc 100644 --- a/app/src/main/java/org/akvo/flow/ui/fragment/SurveyedLocaleListFragment.java +++ b/app/src/main/java/org/akvo/flow/ui/fragment/SurveyedLocaleListFragment.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013-2014 Stichting Akvo (Akvo Foundation) + * Copyright (C) 2013-2015 Stichting Akvo (Akvo Foundation) * * This file is part of Akvo FLOW. * @@ -16,7 +16,6 @@ package org.akvo.flow.ui.fragment; -import java.text.DecimalFormat; import java.util.Date; import org.akvo.flow.util.GeoUtil; @@ -127,8 +126,8 @@ public void onResume() { mLatitude = loc.getLatitude(); mLongitude = loc.getLongitude(); } + mLocationManager.requestLocationUpdates(provider, 1000, 0, this); } - mLocationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 1000, 0, this); // Listen for data sync updates, so we can update the UI accordingly getActivity().registerReceiver(dataSyncReceiver, diff --git a/app/src/main/java/org/akvo/flow/ui/view/MediaQuestionView.java b/app/src/main/java/org/akvo/flow/ui/view/MediaQuestionView.java index 08a6880ed..4c3dc7472 100644 --- a/app/src/main/java/org/akvo/flow/ui/view/MediaQuestionView.java +++ b/app/src/main/java/org/akvo/flow/ui/view/MediaQuestionView.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010-2014 Stichting Akvo (Akvo Foundation) + * Copyright (C) 2010-2015 Stichting Akvo (Akvo Foundation) * * This file is part of Akvo FLOW. * @@ -30,14 +30,17 @@ import android.view.Window; import android.widget.Button; import android.widget.ImageView; +import android.widget.ProgressBar; import android.widget.Toast; import org.akvo.flow.R; +import org.akvo.flow.async.MediaSyncTask; import org.akvo.flow.domain.Question; import org.akvo.flow.domain.QuestionResponse; import org.akvo.flow.event.QuestionInteractionEvent; import org.akvo.flow.event.SurveyListener; import org.akvo.flow.util.ConstantUtil; +import org.akvo.flow.util.FileUtil; import org.akvo.flow.util.ImageUtil; import java.io.File; @@ -48,9 +51,11 @@ * * @author Christopher Fagiani */ -public class MediaQuestionView extends QuestionView implements OnClickListener { +public class MediaQuestionView extends QuestionView implements OnClickListener, MediaSyncTask.DownloadListener { private Button mMediaButton; private ImageView mImage; + private ProgressBar mProgressBar; + private View mDownloadBtn; private String mMediaType; public MediaQuestionView(Context context, Question q, SurveyListener surveyListener, @@ -64,7 +69,9 @@ private void init() { setQuestionView(R.layout.media_question_view); mMediaButton = (Button)findViewById(R.id.media_btn); - mImage = (ImageView)findViewById(R.id.completed_iv); + mImage = (ImageView)findViewById(R.id.image); + mProgressBar = (ProgressBar)findViewById(R.id.progress); + mDownloadBtn = findViewById(R.id.download); if (isImage()) { mMediaButton.setText(R.string.takephoto); @@ -77,13 +84,21 @@ private void init() { } mImage.setOnClickListener(this); - mImage.setVisibility(View.INVISIBLE); + mDownloadBtn.setOnClickListener(this); + + hideDownloadOptions(); + } + + private void hideDownloadOptions() { + mProgressBar.setVisibility(View.GONE); + mDownloadBtn.setVisibility(View.GONE); } /** * handle the action button click */ public void onClick(View v) { + // TODO: Use switch instead of if-else if (v == mImage) { String filename = getResponse() != null ? getResponse().getValue() : null; if (TextUtils.isEmpty(filename) || !(new File(filename).exists())) { @@ -111,6 +126,12 @@ public void onClick(View v) { } else { notifyQuestionListeners(QuestionInteractionEvent.TAKE_VIDEO_EVENT); } + } else if (v == mDownloadBtn) { + mDownloadBtn.setVisibility(GONE); + mProgressBar.setVisibility(VISIBLE); + + MediaSyncTask downloadTask = new MediaSyncTask(getContext(), new File(getResponse().getValue()), this); + downloadTask.execute(); } } @@ -136,6 +157,20 @@ public void questionComplete(Bundle mediaData) { @Override public void rehydrate(QuestionResponse resp) { super.rehydrate(resp); + + // We now check whether the file is found in the local filesystem, and update the path if it's not + String filename = getResponse() != null ? getResponse().getValue() : null; + if (!TextUtils.isEmpty(filename)) { + File file = new File(filename); + if (!file.exists() && isReadOnly()) + // Looks like the image is not present in the filesystem (i.e. remote URL) + // Update response, matching the local path. Note: In the future, media responses should + // not leak filesystem paths, for these are not guaranteed to be homogeneous in all devices. + file = new File(FileUtil.getFilesDir(FileUtil.FileType.MEDIA), file.getName()); + setResponse(new QuestionResponse(file.getAbsolutePath(), + isImage() ? ConstantUtil.IMAGE_RESPONSE_TYPE : ConstantUtil.VIDEO_RESPONSE_TYPE, + getQuestion().getId())); + } displayThumbnail(); } @@ -145,7 +180,8 @@ public void rehydrate(QuestionResponse resp) { @Override public void resetQuestion(boolean fireEvent) { super.resetQuestion(fireEvent); - mImage.setVisibility(View.GONE); + mImage.setImageDrawable(null); + hideDownloadOptions(); } @Override @@ -153,14 +189,15 @@ public void captureResponse(boolean suppressListeners) { } private void displayThumbnail() { + hideDownloadOptions(); + String filename = getResponse() != null ? getResponse().getValue() : null; if (TextUtils.isEmpty(filename)) { return; } if (!new File(filename).exists()) { - // Looks like the image is not present in the filesystem (i.e. remote URL) - // TODO: Handle image downloads - mImage.setImageResource(R.drawable.checkmark); + mImage.setImageResource(R.drawable.blurry_image); + mDownloadBtn.setVisibility(VISIBLE); } else if (isImage()) { // Image thumbnail ImageUtil.displayImage(mImage, filename); @@ -169,11 +206,18 @@ private void displayThumbnail() { mImage.setImageBitmap(ThumbnailUtils.createVideoThumbnail( filename, MediaStore.Video.Thumbnails.MINI_KIND)); } - mImage.setVisibility(View.VISIBLE); } private boolean isImage() { return ConstantUtil.PHOTO_QUESTION_TYPE.equals(mMediaType); } + @Override + public void onResourceDownload(boolean done) { + if (!done) { + Toast.makeText(getContext(), R.string.error_img_preview, Toast.LENGTH_SHORT).show(); + } + displayThumbnail(); + } + } diff --git a/app/src/main/java/org/akvo/flow/util/PlatformUtil.java b/app/src/main/java/org/akvo/flow/util/PlatformUtil.java index 7b6e2be6e..cee5178ed 100644 --- a/app/src/main/java/org/akvo/flow/util/PlatformUtil.java +++ b/app/src/main/java/org/akvo/flow/util/PlatformUtil.java @@ -22,6 +22,7 @@ import android.content.res.Resources; import android.content.res.TypedArray; import android.net.Uri; +import android.provider.Settings.Secure; import android.util.Log; import android.util.TypedValue; @@ -120,4 +121,8 @@ public static String recordUuid(){ return base32Id.substring(0, 4) + "-" + base32Id.substring(4, 8) + "-" + base32Id.substring(8); } + public static String getAndroidID(Context context) { + return Secure.getString(context.getContentResolver(), Secure.ANDROID_ID); + } + } diff --git a/app/src/main/java/org/akvo/flow/util/StatusUtil.java b/app/src/main/java/org/akvo/flow/util/StatusUtil.java index 17fd90d45..9fc1d1915 100644 --- a/app/src/main/java/org/akvo/flow/util/StatusUtil.java +++ b/app/src/main/java/org/akvo/flow/util/StatusUtil.java @@ -148,6 +148,13 @@ private static boolean syncOver3G(Context context) { return use3G; } + public static String getDeviceId(Context context) { + SurveyDbAdapter db = new SurveyDbAdapter(context).open(); + String value = db.getPreference(ConstantUtil.DEVICE_IDENT_KEY); + db.close(); + return value; + } + /** * Get the specified server URL. If no custom server has been set (debug), * the default one will be returned. diff --git a/app/src/main/res/drawable-hdpi/blurry_image.png b/app/src/main/res/drawable-hdpi/blurry_image.png new file mode 100644 index 000000000..de92f1c1e Binary files /dev/null and b/app/src/main/res/drawable-hdpi/blurry_image.png differ diff --git a/app/src/main/res/drawable-hdpi/download.png b/app/src/main/res/drawable-hdpi/download.png new file mode 100644 index 000000000..2fa2bd9cb Binary files /dev/null and b/app/src/main/res/drawable-hdpi/download.png differ diff --git a/app/src/main/res/layout/media_question_view.xml b/app/src/main/res/layout/media_question_view.xml index 994425d48..342059e4a 100644 --- a/app/src/main/res/layout/media_question_view.xml +++ b/app/src/main/res/layout/media_question_view.xml @@ -16,16 +16,35 @@ android:layout_height="wrap_content" android:text="Take Photo" /> - + android:layout_gravity="center" + android:gravity="center"> + + + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 3f7dd62ac..0a32d1e25 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -207,6 +207,8 @@ 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