package org.sana.android.fragment;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.UUID;
import javax.xml.parsers.ParserConfigurationException;
import org.json.JSONException;
import org.json.JSONObject;
import org.json.JSONTokener;
import org.sana.R;
import org.sana.android.Constants;
import org.sana.android.activity.BaseRunner;
import org.sana.android.activity.ProcedureRunner;
import org.sana.android.app.Locales;
import org.sana.android.app.State.Keys;
import org.sana.android.content.Intents;
import org.sana.android.content.Uris;
import org.sana.android.db.EventDAO;
import org.sana.android.db.ModelWrapper;
import org.sana.android.db.PatientInfo;
import org.sana.android.db.ProcedureDAO;
import org.sana.android.db.SanaDB.ImageSQLFormat;
import org.sana.android.media.EducationResource.Audience;
import org.sana.android.net.MDSInterface;
import org.sana.android.procedure.Procedure;
import org.sana.android.procedure.ProcedureElement;
import org.sana.android.procedure.ProcedurePage;
import org.sana.android.procedure.ProcedureParseException;
import org.sana.android.procedure.ValidationError;
import org.sana.android.provider.Encounters;
import org.sana.android.provider.Events.EventType;
import org.sana.android.provider.Observations;
import org.sana.android.provider.Patients;
import org.sana.android.provider.Procedures;
import org.sana.android.provider.Subjects;
import org.sana.android.service.BackgroundUploader;
import org.sana.android.service.ServiceConnector;
import org.sana.android.service.ServiceListener;
import org.sana.android.service.impl.InstrumentationService;
import org.sana.android.task.PatientLookupListener;
import org.sana.android.task.PatientLookupTask;
import org.sana.android.util.Logf;
import org.sana.android.util.SanaUtil;
import org.sana.util.UUIDUtil;
import org.xml.sax.SAXException;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.ActivityNotFoundException;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.AsyncTask.Status;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.text.TextUtils;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.AnimationUtils;
import android.view.inputmethod.InputMethodManager;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import android.widget.ViewAnimator;
/**
* Base class for running either a new encounter or a new patient. Individual
* procedure steps are rendered to a view which is wrapped in a container which
* presents buttons for paging. Additional logic is built into this class to
* handle launching and capturing returned values from Activities used to
* capture data along with initiating procedure saving, reloading, and
* uploading.
*
* @author Sana Development Team
*/
public abstract class BaseRunnerFragment extends BaseFragment implements View.OnClickListener,
ServiceListener<BackgroundUploader>, PatientLookupListener{
public static final String TAG = BaseRunnerFragment.class.getSimpleName();
public static final String INTENT_KEY_STRING = "intentKey";
public static final String INTENT_EXTRAS_KEY = "extras";
public static final String PLUGIN_INTENT_KEY = "pluginIntent";
// Intent
public static final int CAMERA_INTENT_REQUEST_CODE = 1;
public static final int BARCODE_INTENT_REQUEST_CODE = 2;
public static final int INFO_INTENT_REQUEST_CODE = 7;
public static final int PLUGIN_INTENT_REQUEST_CODE = 4;
public static final int IMPLICIT_PLUGIN_INTENT_REQUEST_CODE = 8;
public static final int FLAG_OBJECT_TEMPORARY = 0;
public static final int FLAG_OBJECT_UPDATED = 1;
public static final int FLAG_OBJECT_PERSISTED = 2;
public static interface ProcedureListener{
void onProcedureComplete(Intent data);
void onProcedureCancelled(String message);
}
protected ProcedureListener mProcedureListener = null;
// Views
private Button next, prev, info;
private ViewAnimator baseViews;
// Service
private ServiceConnector mConnector = new ServiceConnector();
private BackgroundUploader mUploadService = null;
// State instance fields
protected Procedure mProcedure = null;
protected Uri uEncounter = Uri.EMPTY;
protected String mSessionKey = "";
protected Uri uSubject = Uri.EMPTY;
protected Uri uProcedure = Uri.EMPTY;
protected Uri uObserver = Uri.EMPTY;
protected Uri uTask = Uri.EMPTY;
protected Uri mData = Uri.EMPTY;
protected int startPage = 0;
protected boolean onDonePage = false;
protected int objectFlag = FLAG_OBJECT_TEMPORARY;
private PatientLookupTask patientLookupTask = null;
protected boolean showCompleteConfirmation = true;
// Activity Methods ///////////////////////////////////////////////////////
/** {@inheritDoc} */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//this.dump();
}
/** {@inheritDoc} */
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
Log.i(TAG, "onCreateView(LayoutInflater,ViewGroup,Bundle");
return inflater.inflate(R.layout.base_runner_fragment, container, false);
}
/*
* (non-Javadoc)
* @see android.support.v4.app.Fragment#onActivityCreated(android.os.Bundle)
*/
@Override
public void onActivityCreated(Bundle instance) {
Log.i(getClassTag(), "onActivityCreated()");
super.onActivityCreated(instance);
try {;
//mConnector.setServiceListener(this);
//mConnector.connect(getActivity());
} catch (Exception e) {
Log.e(getClassTag(),
"Exception starting background upload service: " + e.toString());
e.printStackTrace();
}
this.setRetainInstance(true);
//if(instance != null)
onUpdateAppState(instance);
//else if(getActivity().getIntent() != null)
onUpdateAppState(getActivity().getIntent());
//logEvent(EventType.ENCOUNTER_ACTIVITY_START_OR_RESUME, "");
logEvent(EventType.ENCOUNTER_ACTIVITY_START_OR_RESUME, ((instance != null)?instance.toString():null));
//makeText("Instance is null: " + ((instance != null)? false: true));
loadProcedure(instance);
}
/** {@inheritDoc} */
@Override
public void onDestroy() {
super.onDestroy();
Log.i(TAG, "onDestroy");
if (mConnector != null) {
try {
mConnector.disconnect(getActivity());
} catch (IllegalArgumentException e) {
Log.e(TAG, "While disconnecting service got exception: " + e);
e.printStackTrace();
}
}
if (mProcedure != null) {
mProcedure.clearCachedViews();
}
}
// Dialogs ////////////////////////////////////////////////////////////////
/**
* Loads in a procedure from the bundle passed in to the fragment.
*
* @param instance
*/
protected abstract void loadProcedure(Bundle instance);
/**
* Serializes the current procedure to the database. Takes the answers map
* from the procedure, serializes it to JSON, and stores it. If finished is
* set, then it will set the procedure's row to finished. This will signal
* to the upload service that it is ready for upload.
*
* @param finished -- Whether to set the procedure as ready for upload.
*/
public abstract void storeCurrentProcedure(boolean finished);
public abstract void storeCurrentProcedure(boolean finished, boolean skipHidden);
/** Removes the current procedure form the database. */
public abstract void deleteCurrentProcedure();
/**
* Navigates to the next page.
*
* @return true if navigating to the next page was successful, otherwise,
* false.
*/
public synchronized boolean nextPage() {
Log.i(TAG, "nextPage()");
boolean succeed = true;
try {
mProcedure.current().validate();
} catch (ValidationError e) {
String message = e.getMessage();
logEvent(EventType.ENCOUNTER_PAGE_VALIDATION_FAILED, message);
SanaUtil.createAlertMessage(getActivity(), message);
return false;
}
// Handles any post processing - allows pages to block
if(!handlePostProcessedElements()){
return false;
}
// Save answers
storeCurrentProcedure(false);
if (!mProcedure.hasNextShowable()) {
Log.w(TAG, "...!has next showable");
if (!onDonePage) {
Log.w(TAG, "...!on done page");
baseViews.setInAnimation(getActivity(), R.anim.slide_from_right);
baseViews.setOutAnimation(getActivity(), R.anim.slide_to_left);
onDonePage = true;
if (!isShowCompleteConfirmation()){
getActivity().setProgress(10000);
// finished so we do a final call to persist data
storeCurrentProcedure(true);
uploadProcedureInBackground2();
} else {
baseViews.showNext();
}
getActivity().setProgress(10000);
updateNextPrev();
} else {
Log.d(TAG, "...on done page");
succeed = false;
if (!isShowCompleteConfirmation()){
getActivity().setProgress(10000);
// finished so we do a final call to persist data
storeCurrentProcedure(true);
uploadProcedureInBackground2();
// short circuits any call to update UI
return succeed;
}
}
} else {
Log.d(TAG, "...has next showable");
//mProcedure.advance();
ProcedurePage cp = mProcedure.advanceNext();
while (cp != null){
if(cp.displayForeground()) {
break;
} else {
storeCurrentProcedure(false,false);
}
cp = mProcedure.advanceNext();
}
logEvent(EventType.ENCOUNTER_NEXT_PAGE, Integer.toString(mProcedure.getCurrentIndex()));
// Hide the keyboard
InputMethodManager imm = (InputMethodManager)getActivity().getSystemService(
Context.INPUT_METHOD_SERVICE);
if (imm != null && mProcedure != null && mProcedure.getCachedView() != null)
imm.hideSoftInputFromWindow(mProcedure.getCachedView().getWindowToken(), 0);
Log.d(TAG, "...current page index is: "
+ Integer.toString(mProcedure.getCurrentIndex()));
getActivity().setProgress(currentProg());
// Tell the current page to play its first audio prompt
mProcedure.current().playFirstPrompt();
updateNextPrev();
}
return succeed;
}
/**
* Child classes should override to allow for any additional processing
* that needs to occur prior to advancing.
*
* @return true if any post processing was handled. Default is to always
* return true
*/
protected boolean handlePostProcessedElements() {
return true;
}
/**
* Displays the previous page.
*
* @return true if navigating to the previous page was successful,
* otherwise, false.
*/
public synchronized boolean prevPage() {
boolean succeed = true;
if (onDonePage) {
baseViews.setInAnimation(getActivity(), R.anim.slide_from_left);
baseViews.setOutAnimation(getActivity(), R.anim.slide_to_right);
baseViews.showPrevious();
onDonePage = false;
getActivity().setProgress(currentProg());
// Tell the current page to play its first audio prompt
mProcedure.current().playFirstPrompt();
}
// If was on start of procedures page
// Back button will return a CANCELED message
else if (!mProcedure.hasPrevShowable()) {
// This quits when you hit back and have nowhere else to go back to.
onExitNoSave();
return succeed;
} else if (mProcedure.hasPrevShowable()) {
mProcedure.back();
Log.v("prev", Integer.toString(mProcedure.getCurrentIndex()));
getActivity().setProgress(currentProg());
logEvent(EventType.ENCOUNTER_PREVIOUS_PAGE,
Integer.toString(mProcedure.getCurrentIndex()));
// Save answers
storeCurrentProcedure(false);
// Hide the keyboard
InputMethodManager imm = (InputMethodManager)getActivity().getSystemService(
Context.INPUT_METHOD_SERVICE);
if (imm != null && mProcedure != null && mProcedure.getCachedView() != null)
imm.hideSoftInputFromWindow(mProcedure.getCachedView().getWindowToken(), 0);
// Tell the current page to play its first audio prompt
mProcedure.current().playFirstPrompt();
}
updateNextPrev();
return succeed;
}
/**
* Launches the EducationList activity. Audience may be patient or worker.
*
* @param audience the target audience.
*/
public synchronized void showInfo(Audience audience) {
Log.d(TAG, "Launching Help, audience: " + audience);
// Gets the elements of the current page which have help
Intent i = mProcedure.current().educationResources(audience);
if (i == null) {
Toast.makeText(getActivity(), getString(R.string.dialog_no_help_available),
Toast.LENGTH_SHORT).show();
} else {
try {
startActivityForResult(i, INFO_INTENT_REQUEST_CODE);
} catch (ActivityNotFoundException e) {
Log.e(TAG, e.getMessage());
}
}
}
/** Adds current procedure to queue for upload. */
public void uploadProcedureInBackground() {
storeCurrentProcedure(true);
// First check to make sure procedure has not already been uploaded
if (MDSInterface.isProcedureAlreadyUploaded(uEncounter, getActivity()
.getBaseContext())) {
getActivity().showDialog(ProcedureRunner.DIALOG_ALREADY_UPLOADED);
} else {
Log.i(TAG, "Adding current procedure to background upload queue");
if (mUploadService != null) {
mUploadService.addProcedureToQueue(uEncounter);
}
logEvent(EventType.ENCOUNTER_SAVE_UPLOAD, "");
getActivity().finish();
}
}
public void uploadProcedureInBackground2() {
Log.d(TAG, "uploadProcedureInBackground2() " + uEncounter);
uEncounter = (!Uris.isEmpty(uEncounter))? uEncounter: Uri.EMPTY;
Intent instrumentation = new Intent(getActivity(),InstrumentationService.class);
getActivity().stopService(instrumentation);
Intent data;
if(mProcedureListener != null){
data = getResult(Intents.ACTION_CREATE);
data.putExtra(Intents.EXTRA_ON_COMPLETE, mProcedure.getOnComplete());
mProcedureListener.onProcedureComplete(data);
} else{
data = getResult();
data.putExtra(Intents.EXTRA_ON_COMPLETE, mProcedure.getOnComplete());
getActivity().setResult(Activity.RESULT_OK, data);
getActivity().finish();
}
}
// current progress in the procedure
protected int currentProg() {
int pageCount = mProcedure.getVisiblePageCount();
if (pageCount == 0)
return 10000;
return (int)(10000 * (double)(mProcedure.getCurrentVisibleIndex()) / pageCount);
}
/**
* Logs an event.
*
* @param type
* @param value
*/
public abstract void logEvent(EventType type, String value);
/**
* Logs an exception
*
* @param t
*/
protected void logException(Throwable t) {
String stackTrace = EventDAO.getStackTrace(t);
EventType et = EventType.EXCEPTION;
if (t instanceof OutOfMemoryError) {
et = EventType.OUT_OF_MEMORY;
}
logEvent(et, stackTrace);
}
/** {@inheritDoc} */
@Override
public void onClick(View v) {
Log.i(TAG, "onClick(View)");
if (v == next) {
nextPage();
} else if (v == prev) {
prevPage();
} else if (v == info) {
showInfo(Audience.WORKER);
} else {
switch (v.getId()) {
case R.id.procedure_done_back:
prevPage();
break;
case R.id.procedure_done_upload:
// finished so we do a final call to persist data
storeCurrentProcedure(true);
//uploadProcedureInBackground();
//showUploadingDialog();
uploadProcedureInBackground2();
break;
default:
Log.e(TAG, "Got onClick from unexpected id " + v.getId());
}
}
}
/** For connecting to the BackgroundUploader. */
@Override
public void onConnect(BackgroundUploader uploadService) {
Log.i(TAG, "onServiceConnected");
mUploadService = uploadService;
}
/** For disconnecting from the BackgroundUploader. */
@Override
public void onDisconnect(BackgroundUploader uploadService) {
Log.i(TAG, "onServiceDisconnected");
mUploadService = null;
}
/**
* Call this method from the containing activity so this fragment can handle
* back button behavior.
*/
public void onBackButtonPressed(boolean wasOnDonePage) {
if (!wasOnDonePage) {
prevPage();
}
setContentView(baseViews);
}
// Sets the view of this fragment
private void setContentView(View view) {
// Root view here is a FrameLayout so we know it is a ViewGroup
// TODO should really replace this with a set of fragments
ViewGroup root = (ViewGroup)getView();
root.removeAllViews();
root.addView(view);
}
/** Displays a list of all the questions in the procedure */
public void pageList() {
ListView mList = new ListView(getActivity());
List<String> pList = mProcedure.toStringArray();
// int currentPage = p.getCurrentIndex();
mList.setAdapter(new ArrayAdapter<String>(getActivity(), R.layout.pageslist_item, pList));
mList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
public void onItemClick(AdapterView<?> parentView, View childView, int position, long id) {
onDonePage = false;
// wasOnDonePage = false;
logEvent(EventType.ENCOUNTER_JUMP_TO_QUESTION, Integer.toString(position));
mProcedure.jumpToVisiblePage(position);
baseViews.setDisplayedChild(0);
getActivity().setProgress(currentProg());
updateNextPrev();
setContentView(baseViews);
}
});
setContentView(mList);
}
/** Creates the base view of this object. */
public void createView() {
Log.i(TAG, "createView()");
if (mProcedure == null)
return;
getActivity().setTitle(mProcedure.getTitle());
View procedureView = wrapViewWithInterface(mProcedure.toView(getActivity()));
// Now that the view is active, go to the correct page.
if (mProcedure.getCurrentIndex() != startPage) {
mProcedure.jumpToPage(startPage);
updateNextPrev();
}
baseViews = new ViewAnimator(getActivity());
baseViews.setBackgroundResource(android.R.drawable.alert_dark_frame);
baseViews.setInAnimation(AnimationUtils.loadAnimation(getActivity(),
R.anim.slide_from_right));
baseViews
.setOutAnimation(AnimationUtils.loadAnimation(getActivity(), R.anim.slide_to_left));
baseViews.addView(procedureView);
// This should add it to baseViews, so don't add it manually.
if(isShowCompleteConfirmation()) {
View procedureDonePage = getActivity().getLayoutInflater().inflate(
R.layout.procedure_runner_done, baseViews);
((TextView) procedureDonePage.findViewById(R.id.procedure_done_text)).setTextAppearance(
getActivity(), android.R.style.TextAppearance_Large);
procedureDonePage.findViewById(R.id.procedure_done_back).setOnClickListener(this);
procedureDonePage.findViewById(R.id.procedure_done_upload).setOnClickListener(this);
}
if (onDonePage) {
baseViews.setInAnimation(null);
baseViews.setOutAnimation(null);
baseViews.showNext();
baseViews.setInAnimation(AnimationUtils.loadAnimation(getActivity(),
R.anim.slide_from_right));
baseViews.setOutAnimation(AnimationUtils.loadAnimation(getActivity(),
R.anim.slide_to_left));
}
setContentView(baseViews);
getActivity().setProgressBarVisibility(true);
getActivity().setProgress(0);
}
/**
* Takes a view and wraps it with next/previous buttons for navigating.
*
* @param sub - the view which is to be wrapped
* @return - a new view which is <param>sub</param> wrapped with next/prev
* buttons.
*/
public View wrapViewWithInterface(View sub) {
// View sub = state.current().toView(this);
// RelativeLayout rl = new RelativeLayout(this);
// rl.setLayoutParams( new ViewGroup.LayoutParams(
// LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT ) );
LinearLayout base = new LinearLayout(getActivity());
base.setOrientation(LinearLayout.VERTICAL);
LinearLayout ll = new LinearLayout(getActivity());
ll.setOrientation(LinearLayout.HORIZONTAL);
next = new Button(getActivity());
next.setOnClickListener(this);
info = new Button(getActivity());
info.setOnClickListener(this);
info.setText(getResources().getString(R.string.procedurerunner_info));
info.setText(getResources().getString(R.string.procedurerunner_info));
prev = new Button(getActivity());
prev.setOnClickListener(this);
next.setPadding(5,5,5,5);
prev.setPadding(5,5,5,5);
info.setPadding(5,5,5,5);
updateNextPrev();
// Are we dispalying Info button
boolean showEdu = PreferenceManager.getDefaultSharedPreferences(getActivity()).getBoolean(
Constants.PREFERENCE_EDUCATION_RESOURCE, false);
float nextWeight = showEdu ? 0.333f : 0.5f;
float infoWeight = showEdu ? 0.334f : 0.0f;
float prevWeight = showEdu ? 0.333f : 0.5f;
ll.addView(prev, new LinearLayout.LayoutParams(-2, -1, prevWeight));
// Only show info button if Education Resource Setting is true
if (showEdu)
ll.addView(info, new LinearLayout.LayoutParams(-2, -1, infoWeight));
ll.addView(next, new LinearLayout.LayoutParams(-2, -1, nextWeight));
ll.setWeightSum(1.0f);
// RelativeLayout.LayoutParams ll_lp = new
// RelativeLayout.LayoutParams(-1,100);
// ll_lp.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM);
// rl.addView(ll, ll_lp);
// RelativeLayout.LayoutParams sub_lp = new
// RelativeLayout.LayoutParams(-1,-1);
// sub_lp.addRule(RelativeLayout.ABOVE, ll.getId());
// rl.addView(sub, sub_lp);
// ScrollView sv = new ScrollView(this);
// sv.addView(sub, new ViewGroup.LayoutParams(-1,-1));
// base.addView(sv, new LinearLayout.LayoutParams(-1,-2,0.99f));
ViewGroup parent = (ViewGroup) sub.getParent();
if (parent != null) {
parent.removeView(sub);
}
base.addView(sub, new LinearLayout.LayoutParams(-1, -2, 0.99f));
base.addView(ll, new LinearLayout.LayoutParams(-1, -2, 0.01f));
base.setWeightSum(1.0f);
return base;
}
/** Updates the next and previous page references. */
public void updateNextPrev() {
Locales.updateLocale(getActivity(), getString(R.string.force_locale));
prev.setEnabled(mProcedure.hasPrev());
prev.setText(getResources().getString(R.string.procedurerunner_previous));
if (mProcedure.hasNext()) {
next.setText(getResources().getString(R.string.procedurerunner_next));
} else {
next.setText(getResources().getString(R.string.procedurerunner_done));
}
}
int requested = 0;
/**
* A request for loading a procedure.
*
* @author Sana Development Team
*/
class ProcedureLoadRequest {
Bundle instance = null;
Intent intent = null;
}
/**
* A task for loading a procedure.
*
* @author Sana Development Team
*/
class ProcedureLoaderTask extends AsyncTask<ProcedureLoadRequest, Void, ProcedureLoadResult> {
Bundle instance;
Intent intent;
/** {@inheritDoc} */
@Override
protected void onPreExecute() {
super.onPreExecute();
showProgressDialogFragment(getString(R.string.dialog_loading_procedure));
}
/** {@inheritDoc} */
@Override
protected ProcedureLoadResult doInBackground(ProcedureLoadRequest... params) {
requested++;
Log.v(TAG, "ProcedureLoadRequest count: " + requested);
ProcedureLoadRequest load = params[0];
instance = load.instance;
intent = load.intent;
ProcedureLoadResult result = new ProcedureLoadResult();
if (Uris.isEmpty(uEncounter) && instance == null && !intent.hasExtra("savedProcedureUri")) {
// New Encounter
Uri procedure = intent.getData();
Log.i(TAG, "ProcedureLoadResult.doInBackground() : uri = " + procedure + "(" + procedure.getLastPathSegment() + "), savedUri="
+ uEncounter);
String uuid = procedure.getLastPathSegment();
if(!UUIDUtil.isValid(uuid)){
uuid = ModelWrapper.getUuid(procedure,getActivity().getContentResolver());
procedure = Uris.withAppendedUuid(Procedures.CONTENT_URI, uuid);
}
Log.i(TAG, "preparing to load xml for uri = " + procedure);
String procedureXml = ProcedureDAO.getXMLForProcedure(getActivity(), procedure);
// Record that we are starting a new encounter
logEvent(EventType.ENCOUNTER_LOAD_NEW_ENCOUNTER, procedure.toString());
// make sure we only insert once
if(uEncounter == null || (uEncounter != null && (uEncounter.equals(Uri.EMPTY)))){
Log.w(TAG, "no Encounter: " + uEncounter);
ContentValues cv = new ContentValues();
cv.put(Encounters.Contract.UUID, UUID.randomUUID().toString());
cv.put(Encounters.Contract.SUBJECT, ModelWrapper.getUuid(uSubject, getActivity().getContentResolver()));
cv.put(Encounters.Contract.OBSERVER, ModelWrapper.getUuid(uObserver, getActivity().getContentResolver()));
cv.put(Encounters.Contract.PROCEDURE, procedure.getLastPathSegment());
cv.put(Encounters.Contract.STATE, "");
cv.put(Encounters.Contract.FINISHED, true);
cv.put(Encounters.Contract.UPLOADED, false);
cv.put(Encounters.Contract.UPLOAD_STATUS, 0);
uEncounter = getActivity().getContentResolver().insert(
Encounters.CONTENT_URI, cv);
//String euuid = ModelWrapper.getUuid(uEncounter,getActivity().getContentResolver());
//uEncounter = Uris.withAppendedUuid(Encounters.CONTENT_URI, euuid);
Log.w(TAG, "inserted Encounter: " + uEncounter);
} else {
Log.w(TAG, "using Encounter: " + uEncounter);
}
Log.w(TAG, "current Encounter: " + uEncounter);
Procedure p = null;
try {
p = Procedure.fromXMLString(procedureXml);
} catch (IOException e) {
Log.e(TAG, "Error loading procedure from XML: " + e.toString());
e.printStackTrace();
logException(e);
} catch (ParserConfigurationException e) {
Log.e(TAG, "Error loading procedure from XML: " + e.toString());
e.printStackTrace();
logException(e);
} catch (SAXException e) {
Log.e(TAG, "Error loading procedure from XML: " + e.toString());
e.printStackTrace();
logException(e);
} catch (ProcedureParseException e) {
Log.e(TAG, "Error loading procedure from XML: " + e.toString());
e.printStackTrace();
logException(e);
} catch (OutOfMemoryError e) {
Log.e(TAG, "Can't load procedure, out of memory.");
result.errorMessage = "Out of Memory.";
e.printStackTrace();
logException(e);
}
if (p != null) {
p.setInstanceUri(uEncounter);
}
result.p = p;
result.success = p != null;
result.procedureUri = procedure;
result.savedProcedureUri = uEncounter;
} else {
// This is a saved encounter.
//startPage = 0;
PatientInfo pi = null;
if(instance == null && Uris.isEmpty(uEncounter)){
Log.v(TAG, "No instance on warm boot.");
String savedProcedureUri = intent.getStringExtra("savedProcedureUri");
if (!TextUtils.isEmpty(savedProcedureUri))
uEncounter = Uri.parse(savedProcedureUri);
// Record that we are restoring a saved encounter
logEvent(EventType.ENCOUNTER_LOAD_SAVED, String.valueOf(uEncounter));
//startPage = intent.getIntExtra("currentPage", 0);
//onDonePage = intent.getBooleanExtra("onDonePage", false);
//pi = new PatientInfo();
} else {
Log.v(TAG, "Instance present on warm boot.");
logEvent(EventType.ENCOUNTER_LOAD_HOTLOAD, String.valueOf(uEncounter));
//startPage = instance.getInt("currentPage");
//onDonePage = instance.getBoolean("onDonePage");
String savedProcedureUri = intent.getStringExtra("savedProcedureUri");
}
Log.w(TAG, "Page: " + startPage + ", onDonePage: " + onDonePage );
if (uEncounter == null) {
Log.e(TAG, "Couldn't determine the URI to warm boot with, " + "bailing.");
return result;
}
Log.i(TAG, "Warm boot occured for " + uEncounter + ", startPage:"
+ startPage + " onDonePage: " + onDonePage);
Cursor c = null;
String procedureId = "";
String answersJson = "";
try {
c = getActivity().getContentResolver().query(uEncounter, new String[] {
Encounters.Contract.PROCEDURE, Encounters.Contract.STATE
}, null, null, null);
c.moveToFirst();
procedureId = c.getString(0);
answersJson = c.getString(1);
} catch (Exception e) {
Log.e(TAG, e.toString());
e.printStackTrace();
} finally {
if (c != null) {
c.close();
}
}
Map<String, String> answersMap = new HashMap<String, String>();
try {
JSONTokener tokener = new JSONTokener(answersJson);
JSONObject answersDict = new JSONObject(tokener);
Iterator it = answersDict.keys();
while (it.hasNext()) {
String key = (String)it.next();
answersMap.put(key, answersDict.getString(key));
Log.i(TAG, "ProcedureLoaderTask loaded answer '" + key + "' : '"
+ answersDict.getString(key) + "'");
}
} catch (JSONException e) {
Log.e(TAG, "onCreate() -- JSONException " + e.toString());
e.printStackTrace();
}
Uri procedureUri;
if(UUIDUtil.isValid(procedureId)){
procedureUri = Uris.withAppendedUuid(Procedures.CONTENT_URI, procedureId);
} else
procedureUri = ContentUris.withAppendedId(Procedures.CONTENT_URI, Long.parseLong(procedureId));
Log.i(TAG, "preparing to load xml for uri = " + procedureUri);
String procedureXml = ProcedureDAO.getXMLForProcedure(getActivity(), procedureUri);
Procedure procedure = null;
try {
procedure = Procedure.fromXMLString(procedureXml);
procedure.setInstanceUri(uEncounter);
procedure.restoreAnswers(answersMap);
} catch (IOException e) {
Log.e(TAG, "onCreate() -- IOException " + e.toString());
e.printStackTrace();
} catch (ParserConfigurationException e) {
Log.e(TAG, "onCreate() -- couldn't create parser");
e.printStackTrace();
} catch (SAXException e) {
Log.e(TAG, "onCreate() -- Couldn't parse XML");
e.printStackTrace();
} catch (ProcedureParseException e) {
Log.e(TAG, "Error in Procedure.fromXML() : " + e.toString());
e.printStackTrace();
} catch (OutOfMemoryError e) {
Log.e(TAG, "Can't load procedure, out of memory.");
result.errorMessage = "Out of Memory.";
e.printStackTrace();
}
Log.i(TAG, "onCreate() : warm-booted from uri =" + uEncounter);
if (procedure != null && pi != null) {
procedure.setPatientInfo(pi);
}
result.p = procedure;
result.success = procedure != null;
result.savedProcedureUri = uEncounter;
result.procedureUri = procedureUri;
}
return result;
}
/** {@inheritDoc} */
@Override
protected void onPostExecute(ProcedureLoadResult result) {
super.onPostExecute(result);
handleResult(result);
}
protected void handleResult(ProcedureLoadResult result){
requested--;
hideProgressDialogFragment();
if (result != null && result.success) {
mProcedure = result.p;
uEncounter = result.savedProcedureUri;
logEvent(EventType.ENCOUNTER_LOAD_FINISHED, "");
if (mProcedure != null){
mProcedure.setInstanceUri(uEncounter);
boolean useId = getActivity().getResources().getBoolean(
R.bool.display_input_element_id);
Log.d(TAG, "...Setting page display id=" + useId);
mProcedure.setShowQuestionIds(useId);
createView();
}
else
logEvent(EventType.ENCOUNTER_LOAD_FAILED, "Null procedure");
} else {
// Show error
logEvent(EventType.ENCOUNTER_LOAD_FAILED, "");
getActivity().finish();
}
}
}
/**
* The result of loading a procedure
*
* @author Sana Development Team
*/
class ProcedureLoadResult {
Uri procedureUri;
Uri savedProcedureUri;
Procedure p = null;
boolean success = false;
String errorMessage = "";
}
// starts a new patient look up task
private void lookupPatient(String patientId) {
logEvent(EventType.ENCOUNTER_LOOKUP_PATIENT_START, patientId);
// Display progress dialog
String message = String.format(getString(R.string.dialog_look_up_patient), patientId);
showProgressDialogFragment(message);
if (patientLookupTask == null || patientLookupTask.getStatus() == Status.FINISHED) {
patientLookupTask = new PatientLookupTask(getActivity());
patientLookupTask.setPatientLookupListener(this);
patientLookupTask.execute(patientId);
}
}
/**
* Callback to handle when a patient look up succeeds. Will result in an
* alert being displayed prompting the user to confirm that it is the
* correct patient.
*/
public void onPatientLookupSuccess(final PatientInfo patientInfo) {
Log.i(TAG,"onPatientLookupSuccess(PatientInfo)");
logEvent(EventType.ENCOUNTER_LOOKUP_PATIENT_SUCCESS, patientInfo.getPatientIdentifier());
hideProgressDialogFragment();
// TODO: should move error messages to BaseFragment
StringBuilder message = new StringBuilder();
message.append("Found patient record for ID ");
message.append(patientInfo.getPatientIdentifier());
message.append("\n");
message.append("First Name: ");
message.append(patientInfo.getPatientFirstName());
message.append("\n");
message.append("Last Name: ");
message.append(patientInfo.getPatientLastName());
message.append("\n");
message.append("Gender: ");
message.append(patientInfo.getPatientGender());
message.append("\n");
message.append("Is this the correct patient?");
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setMessage(message)
.setCancelable(false)
.setPositiveButton(getResources().getString(R.string.general_yes),
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int id) {
dialog.cancel();
mProcedure.setPatientInfo(patientInfo);
nextPage();
}
})
.setNegativeButton(getResources().getString(R.string.general_no),
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.cancel();
mProcedure.current().getPatientIdElement().setAndRefreshAnswer("");
}
});
AlertDialog alert = builder.create();
if (!getActivity().isFinishing())
alert.show();
}
/**
* Callback to handle when a patient look up fails. Will result in an alert
* being displayed prompting the user to input whether the patient should be
* considered new.
*/
public void onPatientLookupFailure(final String patientIdentifier) {
logEvent(EventType.ENCOUNTER_LOOKUP_PATIENT_FAILED, patientIdentifier);
Log.e(TAG, "Couldn't lookup patient. They might exist, but we don't "
+ "have their details.");
hideProgressDialogFragment();
// TODO: should move error messages to BaseFragment
StringBuilder message = new StringBuilder();
message.append("Could not find patient record for ");
message.append(patientIdentifier);
message.append(". Entering a new patient. Continue?");
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setMessage(message)
.setCancelable(false)
.setPositiveButton(getResources().getString(R.string.general_yes),
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int id) {
dialog.cancel();
PatientInfo pi = new PatientInfo();
pi.setPatientIdentifier(patientIdentifier);
mProcedure.setPatientInfo(pi);
nextPage();
}
})
.setNegativeButton(getResources().getString(R.string.general_no),
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.cancel();
}
});
AlertDialog alert = builder.create();
if (!getActivity().isFinishing())
alert.show();
}
protected void onUpdateAppState(Bundle inState){
Log.w(TAG, "onUpdateAppState(Bundle)");
if(inState == null){
Log.e(TAG, "onUpdateAppState(Bundle)" + " null bundle");
return;
}
Uri uri = Uri.EMPTY;
if(inState.containsKey(Intents.EXTRA_ENCOUNTER)){
uri = inState.getParcelable(Intents.EXTRA_ENCOUNTER);
if(!Uris.isEmpty(uri)) uEncounter = uri;
}
if(inState.containsKey(Intents.EXTRA_SUBJECT)){
uri = inState.getParcelable(Intents.EXTRA_SUBJECT);
if(!Uris.isEmpty(uri)) uSubject = uri;
}
if(inState.containsKey(Intents.EXTRA_PROCEDURE)){
uri = inState.getParcelable(Intents.EXTRA_PROCEDURE);
if(!Uris.isEmpty(uri)) uProcedure = uri;
}
if(inState.containsKey(Intents.EXTRA_OBSERVER)){
uri = inState.getParcelable(Intents.EXTRA_OBSERVER);
if(!Uris.isEmpty(uri)) uObserver = uri;
}
if(inState.containsKey(Intents.EXTRA_TASK)){
uri = inState.getParcelable(Intents.EXTRA_TASK);
if(!Uris.isEmpty(uri)) uTask = uri;
}
if(inState.containsKey("currentPage"))
startPage = inState.getInt("currentPage");
if(inState.containsKey("onDonePage"))
onDonePage = inState.getBoolean("onDonePage");
dump();
Log.w(TAG, "onUpdateAppState(Bundle): EXIT");
}
protected void onUpdateAppState(Intent inState){
Log.w(TAG, "onUpdateAppState(Intent)");
dump();
Uri uri = Uri.EMPTY;
if(inState.hasExtra(Intents.EXTRA_ENCOUNTER)){
uri = inState.getParcelableExtra(Intents.EXTRA_ENCOUNTER);
Log.w(TAG, "onUpdateAppState(Intent) encounter uri -->" + uri);
if(!Uris.isEmpty(uri)) uEncounter = uri;
}
if(inState.hasExtra(Intents.EXTRA_SUBJECT)){
uri = inState.getParcelableExtra(Intents.EXTRA_SUBJECT);
Log.w(TAG, "onUpdateAppState(Intent) subject uri -->" + uri);
if(!Uris.isEmpty(uri)) uSubject = uri;
}
if(inState.hasExtra(Intents.EXTRA_PROCEDURE)){
uri = inState.getParcelableExtra(Intents.EXTRA_PROCEDURE);
Log.w(TAG, "onUpdateAppState(Intent) procedure uri -->" + uri);
if(!Uris.isEmpty(uri)) uProcedure = uri;
}
if(inState.hasExtra(Intents.EXTRA_OBSERVER)){
uri = inState.getParcelableExtra(Intents.EXTRA_OBSERVER);
Log.w(TAG, "onUpdateAppState(Intent) observer uri -->" + uri);
if(!Uris.isEmpty(uri)) uObserver = uri;
}
if(inState.hasExtra(Intents.EXTRA_TASK)){
uri = inState.getParcelableExtra(Intents.EXTRA_TASK);
Log.w(TAG, "onUpdateAppState(Intent) task uri -->" + uri);
if(!Uris.isEmpty(uri)) uTask = uri;
}
if(inState.hasExtra("currentPage"))
startPage = inState.getIntExtra("currentPage", 0);
if(inState.hasExtra("onDonePage"))
onDonePage = inState.getBooleanExtra("onDonePage", false);
dump();
Log.w(TAG, "onUpdateAppState(Intent): EXIT");
}
protected void dump(){
Logf.D(this.getClassTag(),"dump()", String.format("{ 'encounter': '%s',"
+" 'observer': '%s', 'subject': '%s', 'procedure': '%s', 'task': '%s' }",
uEncounter, uObserver, uSubject, uProcedure,uTask));
}
protected void dump(Class<?> klazz){
Logf.D(klazz.getSimpleName(),"dump()", String.format("{ 'encounter': '%s',"
+" 'observer': '%s', 'subject': '%s', 'procedure': '%s', 'task': '%s' }",
uEncounter, uObserver, uSubject, uProcedure, uTask));
}
protected String getClassTag(){
return BaseRunnerFragment.class.getSimpleName();
}
/**
* Returns the value of the session key. Warning: any key returned must be
* authenticated with the session service.
* @return
*/
protected String getSessionKey(){
return mSessionKey;
}
/**
* Sets the value of the session key. Warning: this method does not make
* any atempt to validate whether the session is authenticated.
* @param sessionKey
*/
protected void setSessionKey(String sessionKey){
mSessionKey = sessionKey;
}
/**
* Writes the state fields for this component to a bundle.
* Currently this writes the following from the Bundle
* <ul>
* <li>instance key</li>
* <li>session key</li>
* <li>current encounter</li>
* <li>current subject</li>
* <li>current observer</li>
* <li>current procedure</li>
* </ul>
* @param outState
*/
protected void onSaveAppState(Bundle outState){
Log.w(TAG, "onSaveAppState()");
dump();
outState.putString(Keys.SESSION_KEY, mSessionKey);
outState.putParcelable(Intents.EXTRA_ENCOUNTER, uEncounter);
outState.putParcelable(Intents.EXTRA_SUBJECT, uSubject);
outState.putParcelable(Intents.EXTRA_PROCEDURE, uProcedure);
outState.putParcelable(Intents.EXTRA_OBSERVER, uObserver);
outState.putParcelable(Intents.EXTRA_TASK, uTask);
outState.putInt("currentPage", ((mProcedure != null)? mProcedure.getCurrentIndex():0));
outState.putBoolean("onDonePage", onDonePage);
dump();
Log.w(TAG, "onSaveAppState(): Exit");
}
/**
* Writes the state fields for this component to an Intent as Extras.
* Currently this writes the following from the Intent.
* <ul>
* <li>instance key</li>
* <li>session key</li>
* <li>current encounter</li>
* <li>current subject</li>
* <li>current observer</li>
* <li>current procedure</li>
* </ul>
* @param outState
*/
protected void onSaveAppState(Intent outState){
dump();
outState.putExtra(Keys.SESSION_KEY, mSessionKey);
outState.putExtra(Intents.EXTRA_ENCOUNTER, uEncounter);
outState.putExtra(Intents.EXTRA_SUBJECT, uSubject);
outState.putExtra(Intents.EXTRA_PROCEDURE, uProcedure);
outState.putExtra(Intents.EXTRA_OBSERVER, uObserver);
outState.putExtra(Intents.EXTRA_TASK, uTask);
outState.putExtra("currentPage", ((mProcedure != null) ? mProcedure.getCurrentIndex() : 0));
outState.putExtra("onDonePage", onDonePage);
}
public Uri getData(){
return mData;
}
public void setData(Uri uri){
mData = uri;
}
public boolean setValue(int pageIndex, String elementId, String value){
return mProcedure.setValue(pageIndex, elementId, value);
}
@Override
public void onSaveInstanceState(Bundle outState){
super.onSaveInstanceState(outState);
onSaveAppState(outState);
}
public final void makeText(String message){
Toast.makeText(getActivity(), message, Toast.LENGTH_LONG);
}
public void onRestoreInstanceState(Bundle inState){
this.onUpdateAppState(inState);
}
public int getCurrentPage(){
return (mProcedure != null)? mProcedure.getCurrentIndex():0;
}
public boolean onDonePage(){
return onDonePage;
}
protected void showUploadingDialog(){
if(getActivity() instanceof BaseRunner){
BaseRunner runner = (BaseRunner) getActivity();
runner.setUploading(true);
runner.showUploadingDialog();
}
}
public void setProcedureListener(ProcedureListener listener){
mProcedureListener = listener;
}
public Intent getResult(){
Intent result = new Intent();
onSaveAppState(result);
return result;
}
public Intent getResult(String action){
String uuid = ModelWrapper.getUuid(uEncounter,getActivity().getContentResolver());
Uri uri = Uris.withAppendedUuid(Encounters.CONTENT_URI, uuid);
uEncounter = uri;
return getResult(action,uri);
}
public Intent getResult(String action, Uri uri){
Intent result = new Intent(action,uri);
onSaveAppState(result);
return result;
}
public boolean isShowCompleteConfirmation() {
return showCompleteConfirmation;
}
/**
* Handles exit and clean up with no save.
*/
protected void onExitNoSave(){
// This quits when you hit back and have nowhere else to go back to.
getActivity().setResult(Activity.RESULT_CANCELED, null);
logEvent(EventType.ENCOUNTER_EXIT_NO_SAVE, "");
getActivity().finish();
}
}