/******************************************************************************* * Copyright 2011 The Regents of the University of California * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. ******************************************************************************/ package org.ohmage.activity; import android.app.Activity; import android.app.AlertDialog; import android.app.Dialog; import android.content.ContentResolver; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.res.Resources.NotFoundException; import android.location.Location; import android.location.LocationListener; import android.location.LocationManager; import android.net.Uri; import android.os.Bundle; import android.os.Handler; import android.text.format.DateUtils; import android.text.method.ScrollingMovementMethod; import android.view.LayoutInflater; import android.view.View; import android.view.View.OnClickListener; import android.view.inputmethod.InputMethodManager; import android.widget.Button; import android.widget.CheckBox; import android.widget.FrameLayout; import android.widget.ProgressBar; import android.widget.ScrollView; import android.widget.TextView; import android.widget.Toast; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import org.ohmage.AccountHelper; import org.ohmage.CampaignPreferencesHelper; import org.ohmage.ConfigHelper; import org.ohmage.OhmageApplication; import org.ohmage.PromptXmlParser; import org.ohmage.R; import org.ohmage.UserPreferencesHelper; import org.ohmage.conditionevaluator.DataPoint; import org.ohmage.conditionevaluator.DataPoint.PromptType; import org.ohmage.conditionevaluator.DataPointConditionEvaluator; import org.ohmage.db.DbContract.Responses; import org.ohmage.db.Models.Campaign; import org.ohmage.db.Models.Response; import org.ohmage.logprobe.Analytics; import org.ohmage.logprobe.Log; import org.ohmage.logprobe.LogProbe.Status; import org.ohmage.logprobe.OhmageAnalytics; import org.ohmage.prompt.AbstractPrompt; import org.ohmage.prompt.Message; import org.ohmage.prompt.Prompt; import org.ohmage.prompt.RepeatableSetHeader; import org.ohmage.prompt.RepeatableSetTerminator; import org.ohmage.prompt.SurveyElement; import org.ohmage.prompt.hoursbeforenow.HoursBeforeNowPrompt; import org.ohmage.prompt.media.MediaPrompt; import org.ohmage.prompt.media.PhotoPrompt; import org.ohmage.prompt.media.VideoPrompt; import org.ohmage.prompt.multichoice.MultiChoicePrompt; import org.ohmage.prompt.multichoicecustom.MultiChoiceCustomPrompt; import org.ohmage.prompt.number.NumberPrompt; import org.ohmage.prompt.singlechoice.SingleChoicePrompt; import org.ohmage.prompt.singlechoicecustom.SingleChoiceCustomPrompt; import org.ohmage.prompt.text.TextPrompt; import org.ohmage.prompt.timestamp.TimestampPrompt; import org.ohmage.service.SurveyGeotagService; import org.ohmage.service.WakefulService; import org.ohmage.triggers.glue.TriggerFramework; import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Calendar; import java.util.List; import java.util.TimeZone; import java.util.UUID; public class SurveyActivity extends Activity implements LocationListener { private static final String TAG = "SurveyActivity"; private static final int DIALOG_CANCEL_ID = 0; private static final int DIALOG_INSTRUCTIONS_ID = 1; protected static final int PROMPT_RESULT = 0; private TextView mSurveyTitleText; private ProgressBar mProgressBar; private TextView mPromptText; private FrameLayout mPromptFrame; private Button mPrevButton; private Button mSkipButton; private Button mNextButton; private List<SurveyElement> mSurveyElements; //private List<PromptResponse> mResponses; private int mCurrentPosition; private String mCampaignUrn; private String mSurveyId; private String mSurveyTitle; private String mSurveySubmitText; private long mLaunchTime; private boolean mReachedEnd; private boolean mSurveyFinished = false; private String mLastSeenRepeatableSetId; private LocationManager mLocManager; private final Handler mHandler = new Handler(); private String mInstructions; private CampaignPreferencesHelper mCampaignPref; public String getSurveyId() { return mSurveyId; } public String getCampaignUrn() { return mCampaignUrn; } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); if(getIntent().hasExtra("campaign_urn")) { mCampaignUrn = getIntent().getStringExtra("campaign_urn"); } else if(ConfigHelper.isSingleCampaignMode()) { mCampaignUrn = Campaign.getSingleCampaign(this); } else { throw new RuntimeException("The campaign urn must be passed to the Survey Activity"); } mCampaignPref = new CampaignPreferencesHelper(this, mCampaignUrn); mSurveyId = getIntent().getStringExtra("survey_id"); mSurveyTitle = getIntent().getStringExtra("survey_title"); mSurveySubmitText = getIntent().getStringExtra("survey_submit_text"); // Create the location manager and start listening to the GPS mLocManager = (LocationManager) getSystemService(LOCATION_SERVICE); NonConfigurationInstance instance = (NonConfigurationInstance) getLastNonConfigurationInstance(); if (instance == null) { SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); Calendar now = Calendar.getInstance(); mLaunchTime = now.getTimeInMillis(); final UserPreferencesHelper preferencesHelper = new UserPreferencesHelper(this); if (preferencesHelper.isUserDisabled()) { ((OhmageApplication) getApplication()).resetAll(); } if (!AccountHelper.accountExists()) { Log.v(TAG, "no credentials saved, so launch Login"); startActivity(AccountHelper.getLoginIntent(this)); finish(); return; } else { mInstructions = null; try { mInstructions = PromptXmlParser.parseCampaignInstructions(Campaign .loadCampaignXml(this, mCampaignUrn)); } catch (XmlPullParserException e) { Log.e(TAG, "Error parsing campaign instructions from xml", e); } catch (IOException e) { Log.e(TAG, "Error parsing campaign instructions from xml", e); } if (mInstructions != null && mCampaignPref.showInstructions()) showDialog(DIALOG_INSTRUCTIONS_ID); mSurveyElements = null; try { mSurveyElements = PromptXmlParser.parseSurveyElements(Campaign.loadCampaignXml(this, mCampaignUrn), mSurveyId); } catch (NotFoundException e) { Log.e(TAG, "Error parsing prompts from xml", e); } catch (XmlPullParserException e) { Log.e(TAG, "Error parsing prompts from xml", e); } catch (IOException e) { Log.e(TAG, "Error parsing prompts from xml", e); } if(mSurveyElements == null || mSurveyElements.isEmpty()) { // If there are no survey elements, something is wrong finish(); Toast.makeText(this, R.string.invalid_survey, Toast.LENGTH_SHORT).show(); return; } mCurrentPosition = 0; mReachedEnd = false; mLastSeenRepeatableSetId = ""; } } else { mSurveyElements = instance.surveyElements; mCurrentPosition = instance.index; mLaunchTime = instance.launchTime; mReachedEnd = instance.reachedEnd; mLastSeenRepeatableSetId = instance.lastSeenRepeatableSetId; mLastElement = instance.lastElement; mSurveyFinished = instance.surveyFinished; mInstructions = instance.instructions; } setContentView(R.layout.survey_activity); mSurveyTitleText = (TextView) findViewById(R.id.survey_title_text); mProgressBar = (ProgressBar) findViewById(R.id.progress_bar); mPromptText = (TextView) findViewById(R.id.prompt_text); mPromptText.setMovementMethod(ScrollingMovementMethod.getInstance()); mPromptFrame = (FrameLayout) findViewById(R.id.prompt_frame); mPrevButton = (Button) findViewById(R.id.prev_button); mSkipButton = (Button) findViewById(R.id.skip_button); mNextButton = (Button) findViewById(R.id.next_button); mPrevButton.setOnClickListener(mClickListener); mSkipButton.setOnClickListener(mClickListener); mNextButton.setOnClickListener(mClickListener); } /** * Stops the gps from running */ Runnable stopUpdates = new Runnable() { @Override public void run() { mLocManager.removeUpdates(SurveyActivity.this); } }; @Override public void onResume() { super.onResume(); Analytics.activity(this, Status.ON); mSurveyTitleText.setText(mSurveyTitle); if (mReachedEnd == false) { showElement(mCurrentPosition); } else { showSubmitScreen(); } // Start the gps location listener to just listen until it gets a lock or until a minute passes and then turn off // This is just to warm up the gps for when the response is actually submitted mLocManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, this); mHandler.removeCallbacks(stopUpdates); mHandler.postDelayed(stopUpdates, DateUtils.MINUTE_IN_MILLIS); } @Override public void onDestroy() { super.onDestroy(); if(mSurveyElements != null && mCurrentPosition < mSurveyElements.size() && mSurveyElements.get(mCurrentPosition) instanceof PhotoPrompt) PhotoPrompt.clearView(mPromptFrame); } @Override public void onLocationChanged(Location location) { if(SurveyGeotagService.locationValid(location)) { // We got a good enough location so lets stop the gps mLocManager.removeUpdates(this); } } @Override public void onStatusChanged(String provider, int status, Bundle extras) { // TODO Auto-generated method stub } @Override public void onProviderEnabled(String provider) { // TODO Auto-generated method stub } @Override public void onProviderDisabled(String provider) { // TODO Auto-generated method stub } @Override public Object onRetainNonConfigurationInstance() { return new NonConfigurationInstance(mSurveyElements, mCurrentPosition, mLaunchTime, mReachedEnd, mLastSeenRepeatableSetId, mLastElement, mSurveyFinished, mInstructions); } private class NonConfigurationInstance { List<SurveyElement> surveyElements; int index; long launchTime; boolean reachedEnd; String lastSeenRepeatableSetId; SurveyElement lastElement; boolean surveyFinished; String instructions; public NonConfigurationInstance(List<SurveyElement> surveyElements, int index, long launchTime, boolean reachedEnd, String lastSeenRepeatableSetId, SurveyElement element, boolean surveyFinished, String instructions) { this.surveyElements = surveyElements; this.index = index; this.launchTime = launchTime; this.reachedEnd = reachedEnd; this.lastSeenRepeatableSetId = lastSeenRepeatableSetId; this.lastElement = element; this.surveyFinished = surveyFinished; this.instructions = instructions; } } private final OnClickListener mClickListener = new OnClickListener() { @Override public void onClick(View v) { if (mCurrentPosition < mSurveyElements.size() && mSurveyElements.get(mCurrentPosition) instanceof AbstractPrompt) { // Tell the current prompt that it is being hidden ((AbstractPrompt)mSurveyElements.get(mCurrentPosition)).onHidden(); } switch (v.getId()) { case R.id.next_button: if (mReachedEnd) { if(!mSurveyFinished) { mSurveyFinished = true; String uuid = storeResponse(); Analytics.widget(v, null, uuid); TriggerFramework.notifySurveyTaken(SurveyActivity.this, mCampaignUrn, mSurveyTitle); UserPreferencesHelper prefs = new UserPreferencesHelper(SurveyActivity.this); prefs.putLastSurveyTimestamp(mSurveyId, System.currentTimeMillis()); finish(); } } else { if (mSurveyElements.get(mCurrentPosition) instanceof Prompt || mSurveyElements.get(mCurrentPosition) instanceof Message) { //show toast if not answered if (mSurveyElements.get(mCurrentPosition) instanceof Message || ((AbstractPrompt)mSurveyElements.get(mCurrentPosition)).isPromptAnswered()) { while (mCurrentPosition < mSurveyElements.size()) { //increment position mCurrentPosition++; //if survey end reached, show submit screen if (mCurrentPosition == mSurveyElements.size()) { mReachedEnd = true; showSubmitScreen(); } else { if (mSurveyElements.get(mCurrentPosition) instanceof Prompt) { //if new position is prompt, check condition String condition = ((AbstractPrompt)mSurveyElements.get(mCurrentPosition)).getCondition(); if (condition == null) condition = ""; if (DataPointConditionEvaluator.evaluateCondition(condition, getPreviousResponses())) { //if true, show new prompt showPrompt(mCurrentPosition); break; } else { //if false, loop up and increment ((AbstractPrompt)mSurveyElements.get(mCurrentPosition)).setDisplayed(false); } } else if (mSurveyElements.get(mCurrentPosition) instanceof RepeatableSetHeader) { //if new position is repeat header, check condition String condition = ((RepeatableSetHeader)mSurveyElements.get(mCurrentPosition)).getCondition(); if (condition == null) condition = ""; if (DataPointConditionEvaluator.evaluateCondition(condition, getPreviousResponses())) { //if true, increment position, show prompt ((RepeatableSetHeader)mSurveyElements.get(mCurrentPosition)).setDisplayed(true); continue; } else { //set repeatable set to NOT_DISPLAYED ((RepeatableSetHeader)mSurveyElements.get(mCurrentPosition)).setDisplayed(false); //if false, increment past repeat set prompts and terminator int promptCount = ((RepeatableSetHeader)mSurveyElements.get(mCurrentPosition)).getPromptCount(); mCurrentPosition += promptCount + 1; //if new position is header with same id, remove items from list ??? continue; } } else if (mSurveyElements.get(mCurrentPosition) instanceof RepeatableSetTerminator) { //if new position is a repeat terminator, show terminator showTerminator(mCurrentPosition); break; } else if (mSurveyElements.get(mCurrentPosition) instanceof Message) { String condition = ((Message)mSurveyElements.get(mCurrentPosition)).getCondition(); if (condition == null) condition = ""; if (DataPointConditionEvaluator.evaluateCondition(condition, getPreviousResponses())) { //if true, show message showMessage(mCurrentPosition); break; } } else { //something is wrong! } } } } else { Toast.makeText(SurveyActivity.this, ((AbstractPrompt)mSurveyElements.get(mCurrentPosition)).getUnansweredPromptText(), Toast.LENGTH_LONG).show(); } } else if (mSurveyElements.get(mCurrentPosition) instanceof RepeatableSetTerminator) { //"next" maps to "terminate" while (mCurrentPosition < mSurveyElements.size()) { //increment position mCurrentPosition++; //if survey end reached, show submit screen if (mCurrentPosition == mSurveyElements.size()) { mReachedEnd = true; showSubmitScreen(); } else { //if element is repeat header with same repeat set id, remove header, prompts, and terminator while (mCurrentPosition < mSurveyElements.size() && mSurveyElements.get(mCurrentPosition) instanceof RepeatableSetHeader && ((RepeatableSetHeader)mSurveyElements.get(mCurrentPosition)).getId().equals(((RepeatableSetTerminator)mSurveyElements.get(mCurrentPosition-1)).getId())) { int count = ((RepeatableSetHeader)mSurveyElements.get(mCurrentPosition)).getPromptCount(); mSurveyElements.remove(mCurrentPosition); //remove header for (int i = 0; i < count; i++) { mSurveyElements.remove(mCurrentPosition); //remove prompts } mSurveyElements.remove(mCurrentPosition); //remove terminator } //repeat until above is not true if (mCurrentPosition == mSurveyElements.size()) { mReachedEnd = true; showSubmitScreen(); } else if (mSurveyElements.get(mCurrentPosition) instanceof Prompt) { //if new position is prompt, check condition String condition = ((AbstractPrompt)mSurveyElements.get(mCurrentPosition)).getCondition(); if (condition == null) condition = ""; if (DataPointConditionEvaluator.evaluateCondition(condition, getPreviousResponses())) { //if true, show new prompt showPrompt(mCurrentPosition); break; } else { //if false, loop up and increment ((AbstractPrompt)mSurveyElements.get(mCurrentPosition)).setDisplayed(false); } } else if (mSurveyElements.get(mCurrentPosition) instanceof RepeatableSetHeader) { //if new position is repeat header, check condition String condition = ((RepeatableSetHeader)mSurveyElements.get(mCurrentPosition)).getCondition(); if (condition == null) condition = ""; if (DataPointConditionEvaluator.evaluateCondition(condition, getPreviousResponses())) { //if true, increment position, show prompt ((RepeatableSetHeader)mSurveyElements.get(mCurrentPosition)).setDisplayed(true); continue; } else { //if false, increment past repeat set prompts and terminator //set repeatable set to NOT_DISPLAYED ((RepeatableSetHeader)mSurveyElements.get(mCurrentPosition)).setDisplayed(false); int promptCount = ((RepeatableSetHeader)mSurveyElements.get(mCurrentPosition)).getPromptCount(); mCurrentPosition += promptCount + 1; //if new position is header with same id, remove items from list ??? continue; } } else if (mSurveyElements.get(mCurrentPosition) instanceof RepeatableSetTerminator) { //if new position is a repeat terminator, show terminator showTerminator(mCurrentPosition); break; } else if (mSurveyElements.get(mCurrentPosition) instanceof Message) { String condition = ((Message)mSurveyElements.get(mCurrentPosition)).getCondition(); if (condition == null) condition = ""; if (DataPointConditionEvaluator.evaluateCondition(condition, getPreviousResponses())) { //if true, show message showMessage(mCurrentPosition); break; } } else { //something is wrong! } } } } } break; case R.id.skip_button: if (mSurveyElements.get(mCurrentPosition) instanceof Prompt) { ((AbstractPrompt)mSurveyElements.get(mCurrentPosition)).setSkipped(true); while (mCurrentPosition < mSurveyElements.size()) { //increment position mCurrentPosition++; //if survey end reached, show submit screen if (mCurrentPosition == mSurveyElements.size()) { mReachedEnd = true; showSubmitScreen(); } else { if (mSurveyElements.get(mCurrentPosition) instanceof Prompt) { //if new position is prompt, check condition String condition = ((AbstractPrompt)mSurveyElements.get(mCurrentPosition)).getCondition(); if (condition == null) condition = ""; if (DataPointConditionEvaluator.evaluateCondition(condition, getPreviousResponses())) { //if true, show new prompt showPrompt(mCurrentPosition); break; } else { //if false, loop up and increment ((AbstractPrompt)mSurveyElements.get(mCurrentPosition)).setDisplayed(false); } } else if (mSurveyElements.get(mCurrentPosition) instanceof RepeatableSetHeader) { //if new position is repeat header, check condition String condition = ((RepeatableSetHeader)mSurveyElements.get(mCurrentPosition)).getCondition(); if (condition == null) condition = ""; if (DataPointConditionEvaluator.evaluateCondition(condition, getPreviousResponses())) { //if true, increment position, show prompt ((RepeatableSetHeader)mSurveyElements.get(mCurrentPosition)).setDisplayed(true); continue; } else { //if false, increment past repeat set prompts and terminator int promptCount = ((RepeatableSetHeader)mSurveyElements.get(mCurrentPosition)).getPromptCount(); mCurrentPosition += promptCount + 1; //if new position is header with same id, remove items from list ??? //set repeatable set to NOT_DISPLAYED ((RepeatableSetHeader)mSurveyElements.get(mCurrentPosition)).setDisplayed(false); continue; } } else if (mSurveyElements.get(mCurrentPosition) instanceof RepeatableSetTerminator) { //if new position is a repeat terminator, show terminator showTerminator(mCurrentPosition); break; } else if (mSurveyElements.get(mCurrentPosition) instanceof Message) { String condition = ((Message)mSurveyElements.get(mCurrentPosition)).getCondition(); if (condition == null) condition = ""; if (DataPointConditionEvaluator.evaluateCondition(condition, getPreviousResponses())) { //if true, show message showMessage(mCurrentPosition); break; } } else { //something is wrong! } } } } else if (mSurveyElements.get(mCurrentPosition) instanceof RepeatableSetTerminator) { //handle skip for repeatable sets } break; case R.id.prev_button: if (mReachedEnd || mSurveyElements.get(mCurrentPosition) instanceof Prompt || mSurveyElements.get(mCurrentPosition) instanceof Message) { mReachedEnd = false; while (mCurrentPosition > 0) { //decrement position mCurrentPosition--; if (mSurveyElements.get(mCurrentPosition) instanceof Prompt) { //if element is prompt, check condition String condition = ((AbstractPrompt)mSurveyElements.get(mCurrentPosition)).getCondition(); if (condition == null) condition = ""; if (DataPointConditionEvaluator.evaluateCondition(condition, getPreviousResponses())) { //if true, show prompt showPrompt(mCurrentPosition); break; } else { //if false, decrement again and loop ((AbstractPrompt)mSurveyElements.get(mCurrentPosition)).setDisplayed(false); } } else if (mSurveyElements.get(mCurrentPosition) instanceof RepeatableSetHeader) { //if element is a repeat header, decrement position and loop continue; } else if (mSurveyElements.get(mCurrentPosition) instanceof RepeatableSetTerminator) { //if element is repeat terminator, check condition String condition = ((RepeatableSetTerminator)mSurveyElements.get(mCurrentPosition)).getCondition(); if (condition == null) condition = ""; if (DataPointConditionEvaluator.evaluateCondition(condition, getPreviousResponses())) { //if true, decrement position, show prompt continue; } else { //if false, decrement past all prompts in set and header int promptCount = ((RepeatableSetTerminator)mSurveyElements.get(mCurrentPosition)).getPromptCount(); mCurrentPosition -= promptCount + 1; continue; } } else if (mSurveyElements.get(mCurrentPosition) instanceof Message) { String condition = ((Message)mSurveyElements.get(mCurrentPosition)).getCondition(); if (condition == null) condition = ""; if (DataPointConditionEvaluator.evaluateCondition(condition, getPreviousResponses())) { //if true, show message showMessage(mCurrentPosition); break; } } } } else if (mSurveyElements.get(mCurrentPosition) instanceof RepeatableSetTerminator) { //"previous" maps to "repeat" int promptCount = ((RepeatableSetTerminator)mSurveyElements.get(mCurrentPosition)).getPromptCount(); mCurrentPosition++; if (mCurrentPosition < mSurveyElements.size() && mSurveyElements.get(mCurrentPosition) instanceof RepeatableSetHeader && ((RepeatableSetHeader)mSurveyElements.get(mCurrentPosition)).getId().equals(((RepeatableSetTerminator)mSurveyElements.get(mCurrentPosition-1)).getId())) { //if next position is header with same repeat set id, increment position mCurrentPosition++; if (mSurveyElements.get(mCurrentPosition) instanceof Prompt) { //if new position is prompt, check condition String condition = ((AbstractPrompt)mSurveyElements.get(mCurrentPosition)).getCondition(); if (condition == null) condition = ""; if (DataPointConditionEvaluator.evaluateCondition(condition, getPreviousResponses())) { //if true, show new prompt showPrompt(mCurrentPosition); break; } else { //if false, loop up and increment ((AbstractPrompt)mSurveyElements.get(mCurrentPosition)).setDisplayed(false); } } else if (mSurveyElements.get(mCurrentPosition) instanceof RepeatableSetTerminator) { //if new position is a repeat terminator, show terminator showTerminator(mCurrentPosition); break; } else if (mSurveyElements.get(mCurrentPosition) instanceof Message) { String condition = ((Message)mSurveyElements.get(mCurrentPosition)).getCondition(); if (condition == null) condition = ""; if (DataPointConditionEvaluator.evaluateCondition(condition, getPreviousResponses())) { //if true, show message showMessage(mCurrentPosition); break; } } else { //something is wrong! } } else { //else, after current position in list, add repeat header, prompts, and terminator, then increment position RepeatableSetHeader newHeader = ((RepeatableSetHeader)mSurveyElements.get(mCurrentPosition - 1 - promptCount - 1)).getCopy(); List<Prompt> newPrompts = new ArrayList<Prompt>(); for (int i = promptCount; i > 0; i--) { Prompt newPrompt = ((AbstractPrompt)mSurveyElements.get(mCurrentPosition - i - 1)).getCopy(); newPrompts.add(newPrompt); } RepeatableSetTerminator newTerminator = ((RepeatableSetTerminator)mSurveyElements.get(mCurrentPosition - 1)).getCopy(); mSurveyElements.add(mCurrentPosition, newTerminator); mSurveyElements.addAll(mCurrentPosition, newPrompts); mSurveyElements.add(mCurrentPosition, newHeader); mCurrentPosition++; if (mSurveyElements.get(mCurrentPosition) instanceof Prompt) { //if new position is prompt, check condition String condition = ((AbstractPrompt)mSurveyElements.get(mCurrentPosition)).getCondition(); if (condition == null) condition = ""; if (DataPointConditionEvaluator.evaluateCondition(condition, getPreviousResponses())) { //if true, show new prompt showPrompt(mCurrentPosition); break; } else { //if false, loop up and increment ((AbstractPrompt)mSurveyElements.get(mCurrentPosition)).setDisplayed(false); } } else if (mSurveyElements.get(mCurrentPosition) instanceof RepeatableSetTerminator) { //if new position is a repeat terminator, show terminator showTerminator(mCurrentPosition); break; } else if (mSurveyElements.get(mCurrentPosition) instanceof Message) { String condition = ((Message)mSurveyElements.get(mCurrentPosition)).getCondition(); if (condition == null) condition = ""; if (DataPointConditionEvaluator.evaluateCondition(condition, getPreviousResponses())) { //if true, show message showMessage(mCurrentPosition); break; } } else { //something is wrong! } } } break; } } }; @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (requestCode == Prompt.REQUEST_CODE && mSurveyElements.get(mCurrentPosition) instanceof Prompt) { ((AbstractPrompt)mSurveyElements.get(mCurrentPosition)).handleActivityResult(this, resultCode, data); } } public void reloadCurrentPrompt() { showPrompt(mCurrentPosition); } private void showSubmitScreen() { handlePromptChangeLogging(null); mNextButton.setText(R.string.submit); mPrevButton.setText(R.string.previous); mPrevButton.setVisibility(View.VISIBLE); mSkipButton.setVisibility(View.INVISIBLE); InputMethodManager imm = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE); imm.hideSoftInputFromWindow(mPromptText.getWindowToken(), 0); mPromptText.setText(R.string.survey_complete); mProgressBar.setProgress(mProgressBar.getMax()); LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE); ScrollView layout = (ScrollView) inflater.inflate(R.layout.submit, null); TextView submitText = (TextView) layout.findViewById(R.id.submit_text); //submitText.setText("Thank you for completing the survey!"); submitText.setText(mSurveySubmitText); mPromptFrame.removeAllViews(); mPromptFrame.addView(layout); } private void showElement(int index) { if (mSurveyElements.get(index) instanceof AbstractPrompt) { showPrompt(index); } else if (mSurveyElements.get(index) instanceof Message) { showMessage(index); } } private void showMessage(int index) { if (mSurveyElements.get(index) instanceof Message) { Message message = (Message)mSurveyElements.get(index); handlePromptChangeLogging(message); mNextButton.setText(R.string.next); mPrevButton.setText(R.string.previous); mSkipButton.setVisibility(View.INVISIBLE); if (index == 0) { mPrevButton.setVisibility(View.INVISIBLE); } else { mPrevButton.setVisibility(View.VISIBLE); } InputMethodManager imm = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE); imm.hideSoftInputFromWindow(mPromptText.getWindowToken(), 0); mPromptText.setText(R.string.survey_message_title); mProgressBar.setProgress(index * mProgressBar.getMax() / mSurveyElements.size()); mPromptFrame.removeAllViews(); message.inflateView(this, mPromptFrame); } else { Log.e(TAG, "trying to showMessage for element that is not a message!"); } } private void showPrompt(int index) { if (mSurveyElements.get(index) instanceof AbstractPrompt) { //If its a photo prompt we need to recycle the image if(mLastElement instanceof PhotoPrompt) PhotoPrompt.clearView(mPromptFrame); AbstractPrompt prompt = (AbstractPrompt)mSurveyElements.get(index); handlePromptChangeLogging(prompt); mNextButton.setText(R.string.next); mPrevButton.setText(R.string.previous); if (index == 0) { mPrevButton.setVisibility(View.INVISIBLE); } else { mPrevButton.setVisibility(View.VISIBLE); } // someone needs to check condition before showing prompt InputMethodManager imm = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE); imm.hideSoftInputFromWindow(mPromptText.getWindowToken(), 0); prompt.setDisplayed(true); prompt.setSkipped(false); // TODO for now I'm casting, but maybe I should move getters/setters to interface? // or just use a list of AbstractPrompt mPromptText.setText(prompt.getPromptText()); mProgressBar.setProgress(index * mProgressBar.getMax() / mSurveyElements.size()); if (prompt.getSkippable().equals("true")) { mSkipButton.setVisibility(View.VISIBLE); mSkipButton.setText(prompt.getSkipLabel()); mSkipButton.invalidate(); } else { mSkipButton.setVisibility(View.INVISIBLE); } mPromptFrame.removeAllViews(); prompt.inflateView(this, mPromptFrame); //mPromptFrame.invalidate(); } else { Log.e(TAG, "trying to showPrompt for element that is not a prompt!"); } } private SurveyElement mLastElement; private void handlePromptChangeLogging(SurveyElement element) { // Don't log anything if its the same element if(element == mLastElement) return; if(mLastElement instanceof AbstractPrompt) { OhmageAnalytics.prompt((AbstractPrompt) mLastElement, Status.OFF); } if(element instanceof AbstractPrompt) { OhmageAnalytics.prompt((AbstractPrompt) element, Status.ON); } mLastElement = element; } private void showTerminator(int index) { if (mSurveyElements.get(index) instanceof RepeatableSetTerminator) { RepeatableSetTerminator terminator = (RepeatableSetTerminator)mSurveyElements.get(index); String terminateText = terminator.getTrueLabel(); String repeatText = terminator.getFalseLabel(); if (terminateText == null || terminateText.equals("")) { terminateText = getString(R.string.survey_repeatable_set_terminate); } if (repeatText == null || repeatText.equals("")) { repeatText = getString(R.string.survey_repeatable_set_repeat); } mNextButton.setText(terminateText); mPrevButton.setText(repeatText); mSkipButton.setVisibility(View.INVISIBLE); if (index == 0) { //this could(should) never happen } InputMethodManager imm = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE); imm.hideSoftInputFromWindow(mPromptText.getWindowToken(), 0); //terminator.setDisplayed(true); //terminator.setSkipped(false); // TODO for now I'm casting, but maybe I should move getters/setters to interface? // or just use a list of AbstractPrompt mPromptText.setText(R.string.survey_repeatable_set_title); mProgressBar.setProgress(index * mProgressBar.getMax() / mSurveyElements.size()); // if (terminator.getSkippable().equals("true")) { // mSkipButton.setVisibility(View.VISIBLE); // } else { // mSkipButton.setVisibility(View.INVISIBLE); // } mPromptFrame.removeAllViews(); terminator.inflateView(this, mPromptFrame); //mPromptFrame.invalidate(); } else { Log.e(TAG, "trying to showTerminator for element that is not a RepeatableSetTerminator!"); } } /*public void setResponse(int index, String id, String value) { // prompt doesn't know it's own index... :( mResponses.set(index, new PromptResponse(id, value)); }*/ private List<DataPoint> getPreviousResponses() { ArrayList<DataPoint> previousResponses = new ArrayList<DataPoint>(); for (int i = 0; i < mCurrentPosition; i++) { if (mSurveyElements.get(i) instanceof AbstractPrompt) { AbstractPrompt prompt = ((AbstractPrompt)mSurveyElements.get(i)); DataPoint dataPoint = new DataPoint(prompt.getId()); dataPoint.setDisplayType(prompt.getDisplayType()); if (prompt instanceof SingleChoicePrompt) { dataPoint.setPromptType("single_choice"); } else if (prompt instanceof MultiChoicePrompt) { dataPoint.setPromptType("multi_choice"); } else if (prompt instanceof MultiChoiceCustomPrompt) { dataPoint.setPromptType("multi_choice_custom"); } else if (prompt instanceof SingleChoiceCustomPrompt) { dataPoint.setPromptType("single_choice_custom"); } else if (prompt instanceof NumberPrompt) { dataPoint.setPromptType("number"); } else if (prompt instanceof HoursBeforeNowPrompt) { dataPoint.setPromptType("hours_before_now"); } else if (prompt instanceof TextPrompt) { dataPoint.setPromptType("text"); } else if (prompt instanceof PhotoPrompt) { dataPoint.setPromptType("photo"); } else if (prompt instanceof VideoPrompt) { dataPoint.setPromptType("video"); } else if (prompt instanceof TimestampPrompt) { dataPoint.setPromptType("timestamp"); } if (prompt.isSkipped()) { dataPoint.setSkipped(); } else if (!prompt.isDisplayed()) { dataPoint.setNotDisplayed(); } else { if (PromptType.single_choice.equals(dataPoint.getPromptType())) { dataPoint.setValue(prompt.getResponseObject()); } else if (PromptType.single_choice_custom.equals(dataPoint.getPromptType())) { dataPoint.setValue(prompt.getResponseObject()); // The condition evaluator needs to know the index of hardcoded options if(prompt instanceof SingleChoiceCustomPrompt) { int idx = ((SingleChoiceCustomPrompt) prompt).getSelectedIndex(); if(idx != -1) dataPoint.setIndex(idx); } } else if (PromptType.multi_choice.equals(dataPoint.getPromptType())) { JSONArray jsonArray; ArrayList<Integer> dataPointValue = new ArrayList<Integer>(); try { jsonArray = (JSONArray)prompt.getResponseObject(); for (int j = 0; j < jsonArray.length(); j++) { dataPointValue.add((Integer)jsonArray.get(j)); } } catch (JSONException e) { // TODO Auto-generated catch block e.printStackTrace(); } dataPoint.setValue(dataPointValue); } else if (PromptType.multi_choice_custom.equals(dataPoint.getPromptType())) { JSONArray jsonArray; ArrayList<String> dataPointValue = new ArrayList<String>(); try { jsonArray = (JSONArray)prompt.getResponseObject(); for (int j = 0; j < jsonArray.length(); j++) { dataPointValue.add((String)jsonArray.get(j)); } } catch (JSONException e) { // TODO Auto-generated catch block e.printStackTrace(); } dataPoint.setValue(dataPointValue); // The condition evaluator needs to know the index of hardcoded options if(prompt instanceof MultiChoiceCustomPrompt) { dataPoint.setIndexes(((MultiChoiceCustomPrompt) prompt).getSelectedIndexes()); } } else if (PromptType.number.equals(dataPoint.getPromptType())) { dataPoint.setValue(prompt.getResponseObject()); } else if (PromptType.hours_before_now.equals(dataPoint.getPromptType())) { dataPoint.setValue(prompt.getResponseObject()); } else if (PromptType.text.equals(dataPoint.getPromptType())) { dataPoint.setValue(prompt.getResponseObject()); } else if (PromptType.photo.equals(dataPoint.getPromptType())) { dataPoint.setValue(prompt.getResponseObject()); } } previousResponses.add(dataPoint); } } return previousResponses; } private String storeResponse() { return storeResponse(this, mSurveyId, mLaunchTime, mCampaignUrn, mSurveyTitle, mSurveyElements); } public static String storeResponse(Context context, String surveyId, long launchTime, String campaignUrn, String surveyTitle, List<SurveyElement> surveyElements) { AccountHelper helper = new AccountHelper(context); String username = helper.getUsername(); SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); Calendar now = Calendar.getInstance(); String date = dateFormat.format(now.getTime()); long time = now.getTimeInMillis(); String timezone = TimeZone.getDefault().getID(); //get launch context from trigger glue JSONObject surveyLaunchContextJson = new JSONObject(); try { surveyLaunchContextJson.put("launch_time", launchTime); surveyLaunchContextJson.put("launch_timezone", timezone); surveyLaunchContextJson.put("active_triggers", TriggerFramework.getActiveTriggerInfo(context, campaignUrn, surveyTitle)); } catch (JSONException e) { Log.e(TAG, "JSONException when trying to generate survey launch context json", e); throw new RuntimeException(e); } String surveyLaunchContext = surveyLaunchContextJson.toString(); JSONArray responseJson = new JSONArray(); JSONArray repeatableSetResponseJson = new JSONArray(); JSONArray iterationResponseJson = new JSONArray(); JSONObject itemJson = null; boolean inRepeatableSet = false; for (int i = 0; i < surveyElements.size(); i++) { if (surveyElements.get(i) instanceof Prompt) { if (!inRepeatableSet) { itemJson = new JSONObject(); try { itemJson.put("prompt_id", ((AbstractPrompt)surveyElements.get(i)).getId()); itemJson.put("value", ((AbstractPrompt)surveyElements.get(i)).getResponseObject()); Object extras = ((AbstractPrompt)surveyElements.get(i)).getExtrasObject(); if (extras != null) { // as of now we don't actually have "extras" we only have "custom_choices" for the custom types // so this is currently only used by single_choice_custom and multi_choice_custom itemJson.put("custom_choices", extras); } } catch (JSONException e) { Log.e(TAG, "JSONException when trying to generate response json", e); throw new RuntimeException(e); } responseJson.put(itemJson); } else { JSONObject subItemJson = new JSONObject(); try { subItemJson.put("prompt_id", ((AbstractPrompt)surveyElements.get(i)).getId()); subItemJson.put("value", ((AbstractPrompt)surveyElements.get(i)).getResponseObject()); Object extras = ((AbstractPrompt)surveyElements.get(i)).getExtrasObject(); if (extras != null) { // as of now we don't actually have "extras" we only have "custom_choices" for the custom types // so this is currently only used by single_choice_custom and multi_choice_custom subItemJson.put("custom_choices", extras); } } catch (JSONException e) { Log.e(TAG, "JSONException when trying to generate response json", e); throw new RuntimeException(e); } iterationResponseJson.put(subItemJson); } } else if (surveyElements.get(i) instanceof RepeatableSetHeader) { inRepeatableSet = true; if (i != 0 && (surveyElements.get(i-1) instanceof RepeatableSetTerminator) && ((RepeatableSetHeader)surveyElements.get(i)).getId().equals(((RepeatableSetTerminator)surveyElements.get(i-1)).getId())) { //continue existing set iterationResponseJson = new JSONArray(); } else { //start new set itemJson = new JSONObject(); try { itemJson.put("repeatable_set_id", ((RepeatableSetHeader)surveyElements.get(i)).getId()); itemJson.put("skipped", "false"); itemJson.put("not_displayed", ((RepeatableSetHeader)surveyElements.get(i)).isDisplayed() ? "false" : "true"); repeatableSetResponseJson = new JSONArray(); iterationResponseJson = new JSONArray(); } catch (JSONException e) { Log.e(TAG, "JSONException when trying to generate response json", e); throw new RuntimeException(e); } } } else if (surveyElements.get(i) instanceof RepeatableSetTerminator) { inRepeatableSet = false; repeatableSetResponseJson.put(iterationResponseJson); try { itemJson.put("responses", repeatableSetResponseJson); } catch (JSONException e) { Log.e(TAG, "JSONException when trying to generate response json", e); throw new RuntimeException(e); } if (!(i+1 < surveyElements.size() && (surveyElements.get(i+1) instanceof RepeatableSetHeader) && ((RepeatableSetHeader)surveyElements.get(i+1)).getId().equals(((RepeatableSetTerminator)surveyElements.get(i)).getId()))) { responseJson.put(itemJson); } } } String response = responseJson.toString(); // insert the response, which indirectly populates the prompt response tables, etc. Response candidate = new Response(); candidate.uuid = UUID.randomUUID().toString(); candidate.campaignUrn = campaignUrn; candidate.username = username; candidate.date = date; candidate.time = time; candidate.timezone = timezone; candidate.surveyId = surveyId; candidate.surveyLaunchContext = surveyLaunchContext; candidate.response = response; candidate.locationStatus = SurveyGeotagService.LOCATION_UNAVAILABLE; candidate.locationLatitude = -1; candidate.locationLongitude = -1; candidate.locationProvider = null; candidate.locationAccuracy = -1; candidate.locationTime = -1; candidate.status = Response.STATUS_WAITING_FOR_LOCATION; ContentResolver cr = context.getContentResolver(); Uri responseUri = cr.insert(Responses.CONTENT_URI, candidate.toCV()); Intent intent = new Intent(context, SurveyGeotagService.class); intent.setData(responseUri); WakefulService.sendWakefulWork(context, intent); // create an intent and broadcast it to any interested receivers Intent i = new Intent("org.ohmage.SURVEY_COMPLETE"); i.putExtra(Responses.CAMPAIGN_URN, campaignUrn); i.putExtra(Responses.RESPONSE_USERNAME, username); i.putExtra(Responses.RESPONSE_DATE, date); i.putExtra(Responses.RESPONSE_TIME, time); i.putExtra(Responses.RESPONSE_TIMEZONE, timezone); i.putExtra(Responses.RESPONSE_LOCATION_STATUS, SurveyGeotagService.LOCATION_UNAVAILABLE); i.putExtra(Responses.RESPONSE_STATUS, Response.STATUS_WAITING_FOR_LOCATION); i.putExtra(Responses.SURVEY_ID, surveyId); i.putExtra(Responses.RESPONSE_SURVEY_LAUNCH_CONTEXT, surveyLaunchContext); i.putExtra(Responses.RESPONSE_JSON, response); context.sendBroadcast(i); return candidate.uuid; } @Override public void onPause() { super.onPause(); Analytics.activity(this, Status.OFF); // If we are finishing if(isFinishing()) { // Stop listenting to the gps mLocManager.removeUpdates(this); //clean up the survey photo prompt if(!mSurveyFinished) { for(SurveyElement element : mSurveyElements) if (element instanceof MediaPrompt) ((MediaPrompt) element).delete(); } } } @Override public void onBackPressed() { showDialog(DIALOG_CANCEL_ID); } @Override protected Dialog onCreateDialog(int id) { Dialog dialog = super.onCreateDialog(id); AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this); switch (id) { case DIALOG_CANCEL_ID: dialogBuilder.setTitle(R.string.discard_survey_title) .setMessage(R.string.discard_survey_message) .setCancelable(true) .setPositiveButton(R.string.discard, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { finish(); } }) .setNegativeButton(R.string.cancel, null); dialog = dialogBuilder.create(); break; case DIALOG_INSTRUCTIONS_ID: View view = getLayoutInflater().inflate(R.layout.checkable_dialog_layout, null); TextView text = (TextView) view.findViewById(R.id.text); final CheckBox skip = (CheckBox) view.findViewById(R.id.skip); text.setText(mInstructions); dialogBuilder .setTitle(R.string.survey_campaign_instructions_title) .setView(view) .setCancelable(true) .setPositiveButton(R.string.continue_string, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { mCampaignPref.setShowInstructions(!skip.isChecked()); } }); dialog = dialogBuilder.create(); break; } return dialog; } }