package org.ohmage.service; import android.content.ContentResolver; import android.content.ContentValues; import android.content.Intent; import android.database.Cursor; import android.net.Uri; import com.commonsware.cwac.wakeful.WakefulIntentService; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import org.ohmage.AccountHelper; import org.ohmage.ConfigHelper; import org.ohmage.NotificationHelper; import org.ohmage.OhmageApi; import org.ohmage.OhmageApi.MediaPart; import org.ohmage.OhmageApi.Result; import org.ohmage.UserPreferencesHelper; import org.ohmage.db.DbContract.Campaigns; import org.ohmage.db.DbContract.PromptResponses; import org.ohmage.db.DbContract.Responses; import org.ohmage.db.DbContract.SurveyPrompts; import org.ohmage.db.DbHelper; import org.ohmage.db.DbHelper.Tables; import org.ohmage.db.Models.Response; import org.ohmage.prompt.AbstractPrompt; import org.ohmage.logprobe.Analytics; import org.ohmage.logprobe.Log; import org.ohmage.logprobe.LogProbe.Status; import java.io.File; import java.util.ArrayList; public class UploadService extends WakefulIntentService { /** Extra to tell the upload service if it is running in the background */ public static final String EXTRA_BACKGROUND = "is_background"; private static final String TAG = "UploadService"; private OhmageApi mApi; private boolean isBackground; public UploadService() { super(TAG); } @Override public void onCreate() { super.onCreate(); Analytics.service(this, Status.ON); } @Override public void onDestroy() { super.onDestroy(); Analytics.service(this, Status.OFF); } @Override protected void doWakefulWork(Intent intent) { if (mApi == null) setOhmageApi(new OhmageApi(this)); isBackground = intent.getBooleanExtra(EXTRA_BACKGROUND, false); String serverUrl = ConfigHelper.serverUrl(); AccountHelper helper = new AccountHelper(this); String username = helper.getUsername(); String hashedPassword = helper.getAuthToken(); boolean uploadErrorOccurred = false; boolean authErrorOccurred = false; DbHelper dbHelper = new DbHelper(this); Uri dataUri = intent.getData(); if (!Responses.isResponseUri(dataUri)) { Log.e(TAG, "Upload service can only be called with a response URI"); return; } ContentResolver cr = getContentResolver(); String[] projection = new String[] { Tables.RESPONSES + "." + Responses._ID, Responses.RESPONSE_UUID, Responses.RESPONSE_DATE, Responses.RESPONSE_TIME, Responses.RESPONSE_TIMEZONE, Responses.RESPONSE_LOCATION_STATUS, Responses.RESPONSE_LOCATION_LATITUDE, Responses.RESPONSE_LOCATION_LONGITUDE, Responses.RESPONSE_LOCATION_PROVIDER, Responses.RESPONSE_LOCATION_ACCURACY, Responses.RESPONSE_LOCATION_TIME, Tables.RESPONSES + "." + Responses.SURVEY_ID, Responses.RESPONSE_SURVEY_LAUNCH_CONTEXT, Responses.RESPONSE_JSON, Tables.RESPONSES + "." + Responses.CAMPAIGN_URN, Campaigns.CAMPAIGN_CREATED }; String select = Responses.RESPONSE_STATUS + "!=" + Response.STATUS_DOWNLOADED + " AND " + Responses.RESPONSE_STATUS + "!=" + Response.STATUS_UPLOADED + " AND " + Responses.RESPONSE_STATUS + "!=" + Response.STATUS_WAITING_FOR_LOCATION; Cursor cursor = cr.query(dataUri, projection, select, null, null); // If there is no data we should just return if (cursor == null) return; else if (!cursor.moveToFirst()) { cursor.close(); return; } ContentValues cv = new ContentValues(); cv.put(Responses.RESPONSE_STATUS, Response.STATUS_QUEUED); cr.update(dataUri, cv, select, null); for (int i = 0; i < cursor.getCount(); i++) { long responseId = cursor.getLong(cursor.getColumnIndex(Responses._ID)); ContentValues values = new ContentValues(); values.put(Responses.RESPONSE_STATUS, Response.STATUS_UPLOADING); cr.update(Responses.buildResponseUri(responseId), values, null, null); // cr.update(Responses.CONTENT_URI, values, Tables.RESPONSES + "." + // Responses._ID + "=" + responseId, null); JSONArray responsesJsonArray = new JSONArray(); JSONObject responseJson = new JSONObject(); final ArrayList<MediaPart> media = new ArrayList<MediaPart>(); try { responseJson.put("survey_key", cursor.getString(cursor.getColumnIndex(Responses.RESPONSE_UUID))); responseJson.put("time", cursor.getLong(cursor.getColumnIndex(Responses.RESPONSE_TIME))); responseJson.put("timezone", cursor.getString(cursor.getColumnIndex(Responses.RESPONSE_TIMEZONE))); String locationStatus = cursor.getString(cursor .getColumnIndex(Responses.RESPONSE_LOCATION_STATUS)); responseJson.put("location_status", locationStatus); if (!locationStatus.equals(SurveyGeotagService.LOCATION_UNAVAILABLE)) { JSONObject locationJson = new JSONObject(); locationJson.put("latitude", cursor.getDouble(cursor .getColumnIndex(Responses.RESPONSE_LOCATION_LATITUDE))); locationJson.put("longitude", cursor.getDouble(cursor .getColumnIndex(Responses.RESPONSE_LOCATION_LONGITUDE))); String provider = cursor.getString(cursor .getColumnIndex(Responses.RESPONSE_LOCATION_PROVIDER)); locationJson.put("provider", provider); Log.i(TAG, "Response uploaded with " + provider + " location"); locationJson.put("accuracy", cursor.getFloat(cursor .getColumnIndex(Responses.RESPONSE_LOCATION_ACCURACY))); locationJson .put("time", cursor.getLong(cursor .getColumnIndex(Responses.RESPONSE_LOCATION_TIME))); locationJson.put("timezone", cursor.getString(cursor.getColumnIndex(Responses.RESPONSE_TIMEZONE))); responseJson.put("location", locationJson); } else { Log.w(TAG, "Response uploaded without a location"); } responseJson.put("survey_id", cursor.getString(cursor.getColumnIndex(Responses.SURVEY_ID))); responseJson.put( "survey_launch_context", new JSONObject(cursor.getString(cursor .getColumnIndex(Responses.RESPONSE_SURVEY_LAUNCH_CONTEXT)))); responseJson.put( "responses", new JSONArray(cursor.getString(cursor .getColumnIndex(Responses.RESPONSE_JSON)))); ContentResolver cr2 = getContentResolver(); Cursor promptsCursor = cr2.query(Responses.buildPromptResponsesUri(responseId), new String[] { PromptResponses.PROMPT_RESPONSE_VALUE, SurveyPrompts.SURVEY_PROMPT_TYPE }, PromptResponses.PROMPT_RESPONSE_VALUE + "!=? AND " + PromptResponses.PROMPT_RESPONSE_VALUE + "!=? AND (" + SurveyPrompts.SURVEY_PROMPT_TYPE + "=? OR " + SurveyPrompts.SURVEY_PROMPT_TYPE + "=?)", new String[] { AbstractPrompt.SKIPPED_VALUE, AbstractPrompt.NOT_DISPLAYED_VALUE, "photo", "video" }, null); while (promptsCursor.moveToNext()) { media.add(new MediaPart(new File(Response.getResponseMediaUploadDir(), promptsCursor.getString(0)), promptsCursor.getString(1))); } promptsCursor.close(); } catch (JSONException e) { throw new RuntimeException(e); } responsesJsonArray.put(responseJson); String campaignUrn = cursor.getString(cursor.getColumnIndex(Responses.CAMPAIGN_URN)); String campaignCreationTimestamp = cursor.getString(cursor .getColumnIndex(Campaigns.CAMPAIGN_CREATED)); OhmageApi.UploadResponse response = mApi.surveyUpload(serverUrl, username, hashedPassword, OhmageApi.CLIENT_NAME, campaignUrn, campaignCreationTimestamp, responsesJsonArray.toString(), media); response.handleError(this); int responseStatus = Response.STATUS_UPLOADED; if (response.getResult() == Result.SUCCESS) { NotificationHelper.hideUploadErrorNotification(this); } else { responseStatus = Response.STATUS_ERROR_OTHER; switch (response.getResult()) { case FAILURE: if (response.hasAuthError()) { responseStatus = Response.STATUS_ERROR_AUTHENTICATION; } else { uploadErrorOccurred = true; if (response.getErrorCodes().contains("0700")) { responseStatus = Response.STATUS_ERROR_CAMPAIGN_NO_EXIST; } else if (response.getErrorCodes().contains("0707")) { responseStatus = Response.STATUS_ERROR_INVALID_USER_ROLE; } else if (response.getErrorCodes().contains("0703")) { responseStatus = Response.STATUS_ERROR_CAMPAIGN_STOPPED; } else if (response.getErrorCodes().contains("0710")) { responseStatus = Response.STATUS_ERROR_CAMPAIGN_OUT_OF_DATE; } } break; case INTERNAL_ERROR: uploadErrorOccurred = true; break; case HTTP_ERROR: responseStatus = Response.STATUS_ERROR_HTTP; break; } } ContentValues cv2 = new ContentValues(); cv2.put(Responses.RESPONSE_STATUS, responseStatus); cr.update(Responses.buildResponseUri(responseId), cv2, null, null); cursor.moveToNext(); } cursor.close(); if (isBackground && uploadErrorOccurred) { NotificationHelper.showUploadErrorNotification(this); } } public void setOhmageApi(OhmageApi api) { mApi = api; } }