/**
* ORcycle, Copyright 2014, 2015, PSU Transportation, Technology, and People Lab.
*
* @author Robin Murray <robin5@pdx.edu> (code)
* @author Miguel Figliozzi <figliozzi@pdx.edu> and ORcycle team (general app
* design and features, report questionnaires and new ORcycle features)
*
* For more information on the project, go to
* http://www.pdx.edu/transportation-lab/orcycle and http://www.pdx.edu/transportation-lab/app-development
*
* Updated/modified for Oregon pilot study and app deployment.
*
* ORcycle is free software: you can redistribute it and/or modify it under the
* terms of the GNU General Public License as published by the Free Software
* Foundation, either version 3 of the License, or any later version.
* ORcycle 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 General Public License for more details.
* You should have received a copy of the GNU General Public License along with
* ORcycle. If not, see <http://www.gnu.org/licenses/>.
*
*/
package edu.pdx.cecs.orcycle;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Map;
import java.util.Map.Entry;
import java.util.zip.GZIPOutputStream;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.AsyncTask;
import android.util.Log;
import android.widget.Toast;
public class UserInfoUploader extends AsyncTask<Long, Integer, Boolean> {
private static final String MODULE_TAG = "UserInfoUploader";
private static final int kSaveProtocolVersion3 = 3;
private static final int kSaveProtocolVersion4 = 4;
private static final String boundary = "cycle*******notedata*******atlanta";
private static final String lineEnd = "\r\n";
private static final String fieldSep = "--cycle*******notedata*******atlanta\r\n";
private static final String FID_QUESTION_ID = "question_id";
private static final String FID_ANSWER_ID = "answer_id";
private static final String FID_ANSWER_OTHER_TEXT = "other_text";
public static final String USER_EMAIL = "email";
public static final String USER_INSTALLED = "installed";
public static final String USER_DEVICE_MODEL = "deviceModel";
public static final String USER_APP_VERSION = "app_version";
private String email;
private String installed; // DateTime
private String deviceModel;
private String appVersion;
private Context mCtx = null;
private String userId = null;
public UserInfoUploader(Context ctx, String userId) {
super();
this.mCtx = ctx;
this.userId = userId;
}
/**
*
* @return
* @throws JSONException
*/
private JSONObject getUserJSON() throws JSONException {
JSONObject userJson = new JSONObject();
userJson.put(USER_EMAIL, email);
userJson.put(USER_INSTALLED, installed);
userJson.put(USER_DEVICE_MODEL, deviceModel);
userJson.put(USER_APP_VERSION, appVersion);
return userJson;
}
/**
*
* @return
* @throws JSONException
*/
private JSONArray getUserResponsesJSON() throws JSONException {
// Create a JSON array to hold all of the answers
JSONArray jsonAnswers = new JSONArray();
SharedPreferences settings = mCtx.getSharedPreferences(UserInfoActivity.PREFS_USER_INFO_UPLOAD, Context.MODE_PRIVATE);
Map<String, ?> prefs = settings.getAll();
String riderTypeOther = null;
String occupationOther = null;
String bikeTypeOther = null;
String genderOther = null;
String ethnicityOther = null;
for (Entry<String, ?> p : prefs.entrySet()) {
int key = Integer.parseInt(p.getKey());
// CharSequence value = (CharSequence) p.getValue();
switch (key) {
case UserInfoActivity.PREF_RIDER_TYPE_OTHER: riderTypeOther = (String) p.getValue(); break;
case UserInfoActivity.PREF_OCCUPATION_OTHER: occupationOther = (String) p.getValue(); break;
case UserInfoActivity.PREF_BIKE_TYPE_OTHER: bikeTypeOther = (String) p.getValue(); break;
case UserInfoActivity.PREF_GENDER_OTHER: genderOther = (String) p.getValue(); break;
case UserInfoActivity.PREF_ETHNICITY_OTHER: ethnicityOther = (String) p.getValue(); break;
}
}
for (Entry<String, ?> p : prefs.entrySet()) {
int key = Integer.parseInt(p.getKey());
// CharSequence value = (CharSequence) p.getValue();
switch (key) {
case UserInfoActivity.PREF_EMAIL:
email = (String) p.getValue();
break;
case UserInfoActivity.PREF_RIDER_ABILITY:
putInt(jsonAnswers, DbQuestions.USER_INFO_RIDER_ABILITY,
DbAnswers.userInfoRiderAbility, -1, null, p);
break;
case UserInfoActivity.PREF_RIDER_TYPE:
putInt(jsonAnswers, DbQuestions.USER_INFO_RIDER_TYPE,
DbAnswers.userInfoRiderType,
DbAnswers.userInfoRiderTypeOther, riderTypeOther, p);
break;
case UserInfoActivity.PREF_CYCLE_FREQUENCY:
putInt(jsonAnswers, DbQuestions.USER_INFO_CYCLING_FREQ,
DbAnswers.userInfoCyclingFreq, -1, null, p);
break;
case UserInfoActivity.PREF_CYCLE_WEATHER:
putInt(jsonAnswers, DbQuestions.USER_INFO_CYCLING_WEATHER,
DbAnswers.userInfoCyclingWeather, -1, null, p);
break;
case UserInfoActivity.PREF_NUM_BIKES:
putInt(jsonAnswers, DbQuestions.USER_INFO_NUM_BIKES,
DbAnswers.userInfoNumBikes, -1, null, p);
break;
case UserInfoActivity.PREF_BIKE_TYPES:
putMultiInt(jsonAnswers, DbQuestions.USER_INFO_BIKE_TYPES,
DbAnswers.userInfoBikeTypes,
DbAnswers.userInfoBikeTypeOther, bikeTypeOther, p);
break;
case UserInfoActivity.PREF_OCCUPATION:
putInt(jsonAnswers, DbQuestions.USER_INFO_OCCUPATION,
DbAnswers.userInfoOccupation,
DbAnswers.userInfoOccupationOther, occupationOther, p);
break;
case UserInfoActivity.PREF_AGE:
putInt(jsonAnswers, DbQuestions.USER_INFO_AGE,
DbAnswers.userInfoAge, -1, null, p);
break;
case UserInfoActivity.PREF_GENDER:
putInt(jsonAnswers, DbQuestions.USER_INFO_GENDER,
DbAnswers.userInfoGender,
DbAnswers.userInfoGenderOther, genderOther, p);
break;
case UserInfoActivity.PREF_VEHICLES:
putInt(jsonAnswers, DbQuestions.USER_INFO_VEHICLES,
DbAnswers.userInfoHHVehicles, -1, null, p);
break;
case UserInfoActivity.PREF_WORKERS:
putInt(jsonAnswers, DbQuestions.USER_INFO_WORKERS,
DbAnswers.userInfoHHWorkers, -1, null, p);
break;
case UserInfoActivity.PREF_ETHNICITY:
putInt(jsonAnswers, DbQuestions.USER_INFO_ETHNICITY,
DbAnswers.userInfoEthnicity,
DbAnswers.userInfoEthnicityOther, ethnicityOther, p);
break;
case UserInfoActivity.PREF_INCOME:
putInt(jsonAnswers, DbQuestions.USER_INFO_INCOME,
DbAnswers.userInfoIncome, -1, null, p);
break;
case UserInfoActivity.PREF_INSTALLED:
installed = (String) p.getValue();
break;
case UserInfoActivity.PREF_DEVICE_MODEL:
deviceModel = (String) p.getValue();
break;
case UserInfoActivity.PREF_APP_VERSION:
appVersion = (String) p.getValue();
break;
}
}
return jsonAnswers;
}
/**
*
* @param jsonAnswers
* @param questionId
* @param answers
* @param entry
*/
private void putInt(JSONArray jsonAnswers, int questionId, int[] answers, int otherId, String otherText, Entry<String, ?> entry) {
try {
int index = ((Integer) entry.getValue()).intValue();
if (index >= 0) {
int answer = answers[index];
JSONObject json = new JSONObject();
// Place values into the JSON object
json.put(FID_QUESTION_ID, questionId);
json.put(FID_ANSWER_ID, answer);
// Assure that whenever other is selected, there is an entry for other text
if ((-1 != otherId) && (answers[index] == otherId) ) {
if (null == otherText) {
json.put(FID_ANSWER_OTHER_TEXT, "");
}
else {
json.put(FID_ANSWER_OTHER_TEXT, otherText);
}
}
// Place JSON objects into the JSON array
jsonAnswers.put(json);
}
}
catch (Exception ex) {
Log.e(MODULE_TAG, ex.getMessage());
}
}
/**
*
* @param jsonAnswers
* @param questionId
* @param answers
* @param entry
*/
private void putMultiInt(JSONArray jsonAnswers, int questionId, int[] answers, int otherId, String otherText, Entry<String, ?> entry) {
try {
String text = (String) entry.getValue();
String[] selections = text.split(",");
int index;
for (String selection : selections) {
try {
index = Integer.parseInt(selection);
if (index >= 0 && index < answers.length) {
int answer = answers[index];
JSONObject json = new JSONObject();
// Place values into the JSON object
json.put(FID_QUESTION_ID, questionId);
json.put(FID_ANSWER_ID, answer);
// Assure that whenever other is selected, there is an entry for other text
if ((-1 != otherId) && (answers[index] == otherId) ) {
if (null == otherText) {
json.put(FID_ANSWER_OTHER_TEXT, "");
}
else {
json.put(FID_ANSWER_OTHER_TEXT, otherText);
}
}
// Place JSON objects into the JSON array
jsonAnswers.put(json);
} else {
Log.e(MODULE_TAG, "Index " + index + " is out of bounds.");
}
} catch (Exception ex) {
Log.e(MODULE_TAG, ex.getMessage());
}
}
}
catch (Exception ex) {
Log.e(MODULE_TAG, ex.getMessage());
}
}
private void putString(JSONArray jsonAnswers, int questionId, int[] answers, Entry<String, ?> entry) {
try {
JSONObject json = new JSONObject();
String text = (String) entry.getValue();
int answer_id = answers[0];
if (null != text) {
text = text.trim();
if (!text.equals("")) {
// Place values into the JSON object
json.put(FID_QUESTION_ID, questionId);
json.put(FID_ANSWER_ID, answer_id);
json.put(FID_ANSWER_OTHER_TEXT, text);
// Place JSON objects into the JSON array
jsonAnswers.put(json);
}
}
}
catch (Exception ex) {
Log.e(MODULE_TAG, ex.getMessage());
}
}
private String getPostDataV3() throws JSONException {
JSONArray userResponses = getUserResponsesJSON();
JSONObject user = getUserJSON();
String deviceId = userId;
String codedPostData =
"&user=" + user.toString() +
"&userRespones=" + userResponses.toString() +
"&version=" + String.valueOf(kSaveProtocolVersion3) +
"&device=" + deviceId;
return codedPostData;
}
private static String convertStreamToString(InputStream is) {
/*
* To convert the InputStream to String we use the
* BufferedReader.readLine() method. We iterate until the BufferedReader
* return null which means there's no more data to read. Each line will
* appended to a StringBuilder and returned as String.
*/
BufferedReader reader = new BufferedReader(new InputStreamReader(is));
StringBuilder sb = new StringBuilder();
String line = null;
try {
while ((line = reader.readLine()) != null) {
sb.append(line + "\n");
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return sb.toString();
}
public static byte[] compress(String string) throws IOException {
ByteArrayOutputStream os = new ByteArrayOutputStream(string.length());
GZIPOutputStream gos = new GZIPOutputStream(os);
gos.write(string.getBytes());
gos.close();
byte[] compressed = os.toByteArray();
os.close();
return compressed;
}
boolean uploadUserInfoV3() {
boolean result = false;
byte[] postBodyDataZipped;
String postBodyData;
try {
postBodyData = getPostDataV3();
} catch (JSONException e) {
e.printStackTrace();
return result;
}
HttpClient client = new DefaultHttpClient();
final String postUrl = mCtx.getResources().getString(R.string.post_url);
HttpPost postRequest = new HttpPost(postUrl);
try {
// Zip Upload!!!
Log.v(MODULE_TAG, "postBodyData: " + postBodyData.toString());
Log.v(MODULE_TAG, "postBodyData Length: " + postBodyData.length());
postBodyDataZipped = compress(postBodyData);
Log.v(MODULE_TAG, "postBodyDataZipped: " + postBodyDataZipped);
Log.v(MODULE_TAG, "postBodyDataZipped Length: " + String.valueOf(postBodyDataZipped.length));
Log.v(MODULE_TAG, "Initializing HTTP POST request to " + postUrl
+ " of size " + String.valueOf(postBodyDataZipped.length)
+ " orig size " + postBodyData.length());
postRequest.setHeader("Cycleatl-Protocol-Version", "3");
postRequest.setHeader("Content-Encoding", "gzip");
postRequest.setHeader("Content-Type",
"application/vnd.cycleatl.trip-v3+form");
// postRequest.setHeader("Content-Length",String.valueOf(postBodyDataZipped.length));
postRequest.setEntity(new ByteArrayEntity(postBodyDataZipped));
HttpResponse response = client.execute(postRequest);
String responseString = convertStreamToString(response.getEntity().getContent());
// Log.v("httpResponse", responseString);
JSONObject responseData = new JSONObject(responseString);
if (responseData.getString("status").equals("success")) {
// TODO: Record somehow that data was uploaded successfully
result = true;
}
} catch (IllegalStateException e) {
e.printStackTrace();
return false;
} catch (IOException e) {
e.printStackTrace();
return false;
} catch (JSONException e) {
e.printStackTrace();
return false;
}
return result;
}
boolean uploadUserInfoV4() {
boolean result = false;
final String postUrl = mCtx.getResources().getString(R.string.post_url);
try {
JSONArray userResponses = getUserResponsesJSON();
URL url = new URL(postUrl);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setDoInput(true); // Allow Inputs
conn.setDoOutput(true); // Allow Outputs
conn.setUseCaches(false); // Don't use a Cached Copy
conn.setRequestMethod("POST");
conn.setRequestProperty("Connection", "Keep-Alive");
conn.setRequestProperty("ENCTYPE", "multipart/form-data");
conn.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundary);
conn.setRequestProperty("Cycleatl-Protocol-Version", "4");
DataOutputStream dos = new DataOutputStream(conn.getOutputStream());
JSONObject jsonUser;
if (null != (jsonUser = getUserJSON())) {
try {
String deviceId = userId;
dos.writeBytes(fieldSep + ContentField("user") + jsonUser.toString() + "\r\n");
dos.writeBytes(fieldSep + ContentField("version") + String.valueOf(kSaveProtocolVersion4) + "\r\n");
dos.writeBytes(fieldSep + ContentField("device") + deviceId + "\r\n");
dos.writeBytes(fieldSep + ContentField("userResponses") + userResponses.toString() + "\r\n");
dos.writeBytes(fieldSep);
dos.flush();
}
catch(Exception ex) {
Log.e(MODULE_TAG, ex.getMessage());
return false;
}
finally {
dos.close();
}
int serverResponseCode = conn.getResponseCode();
String serverResponseMessage = conn.getResponseMessage();
// JSONObject responseData = new JSONObject(serverResponseMessage);
Log.v("Jason", "HTTP Response is : " + serverResponseMessage + ": "
+ serverResponseCode);
if (serverResponseCode == 201 || serverResponseCode == 202) {
// TODO: Record somehow that data was uploaded successfully
result = true;
}
}
else {
result = false;
}
} catch (IllegalStateException e) {
e.printStackTrace();
return false;
} catch (IOException e) {
e.printStackTrace();
return false;
} catch (JSONException e) {
e.printStackTrace();
return false;
}
return result;
}
private String ContentField(String type) {
return "Content-Disposition: form-data; name=\"" + type + "\"\r\n\r\n";
}
@Override
protected Boolean doInBackground(Long... dummy) {
Boolean result = false;
try {
//result = uploadUserInfoV4();
result = uploadUserInfoV4();
}
catch(Exception ex) {
Log.e(MODULE_TAG, ex.getMessage());
}
return result;
}
@Override
protected void onPreExecute() {
Toast.makeText(mCtx.getApplicationContext(),
"Submitting. Thanks for using ORcycle!",
Toast.LENGTH_LONG).show();
}
@Override
protected void onPostExecute(Boolean result) {
try {
if (result) {
Toast.makeText(mCtx.getApplicationContext(),
"User information uploaded successfully.", Toast.LENGTH_SHORT)
.show();
} else {
Toast.makeText(
mCtx.getApplicationContext(),
"ORcycle couldn't upload the information.",
Toast.LENGTH_LONG).show();
}
} catch (Exception e) {
// Just don't toast if the view has gone out of context
}
}
}