package org.commcare.activities; import android.graphics.Color; import android.os.Bundle; import android.util.Log; import android.view.View; import android.view.inputmethod.EditorInfo; import android.widget.Button; import android.widget.EditText; import android.widget.LinearLayout; import android.widget.TextView; import android.widget.Toast; import org.commcare.CommCareApplication; import org.commcare.core.network.ModernHttpRequester; import org.commcare.dalvik.R; import org.commcare.core.interfaces.HttpResponseProcessor; import org.commcare.models.AndroidSessionWrapper; import org.commcare.modern.util.Pair; import org.commcare.session.RemoteQuerySessionManager; import org.commcare.suite.model.DisplayData; import org.commcare.suite.model.DisplayUnit; import org.commcare.tasks.SimpleHttpTask; import org.commcare.tasks.templates.CommCareTaskConnector; import org.commcare.views.ManagedUi; import org.commcare.views.UiElement; import org.commcare.views.ViewUtil; import org.commcare.views.dialogs.CustomProgressDialog; import org.commcare.views.media.MediaLayout; import org.javarosa.core.model.instance.ExternalDataInstance; import org.javarosa.core.services.locale.Localization; import org.javarosa.core.services.locale.Localizer; import org.javarosa.core.util.OrderedHashtable; import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.util.Enumeration; import java.util.HashMap; import java.util.Hashtable; import java.util.Map; /** * Collects 'query datum' in the current session. Prompts user for query * params, makes query to server and stores xml 'fixture' response into current * session. Allows for 'case search and claim' workflow when used inside a * 'remote-request' entry in conjuction with entity select datum and sync * * @author Phillip Mates (pmates@dimagi.com). */ @ManagedUi(R.layout.http_request_layout) public class QueryRequestActivity extends SaveSessionCommCareActivity<QueryRequestActivity> implements HttpResponseProcessor { private static final String TAG = QueryRequestActivity.class.getSimpleName(); private static final String ANSWERED_USER_PROMPTS_KEY = "answered_user_prompts"; private static final String IN_ERROR_STATE_KEY = "in-error-state-key"; private static final String ERROR_MESSAGE_KEY = "error-message-key"; @UiElement(value = R.id.request_button, locale = "query.button") private Button queryButton; @UiElement(value = R.id.error_message) private TextView errorTextView; private boolean inErrorState; private String errorMessage; private RemoteQuerySessionManager remoteQuerySessionManager; private final Hashtable<String, EditText> promptsBoxes = new Hashtable<>(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); AndroidSessionWrapper sessionWrapper = CommCareApplication.instance().getCurrentSessionWrapper(); remoteQuerySessionManager = RemoteQuerySessionManager.buildQuerySessionManager(sessionWrapper.getSession(), sessionWrapper.getEvaluationContext()); if (remoteQuerySessionManager == null) { Log.e(TAG, "Tried to launch remote query activity at wrong time in session."); setResult(RESULT_CANCELED); finish(); } else { loadStateFromSavedInstance(savedInstanceState); setupUI(); } } private void setupUI() { buildPromptUI(); queryButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { ViewUtil.hideVirtualKeyboard(QueryRequestActivity.this); answerPrompts(); makeQueryRequest(); } }); if (inErrorState) { enterErrorState(); } } private void buildPromptUI() { LinearLayout promptsLayout = (LinearLayout)findViewById(R.id.query_prompts); OrderedHashtable<String, DisplayUnit> userInputDisplays = remoteQuerySessionManager.getNeededUserInputDisplays(); int promptCount = 1; for (Enumeration en = userInputDisplays.keys(); en.hasMoreElements(); ) { String promptId = (String)en.nextElement(); boolean isLastPrompt = promptCount++ == userInputDisplays.size(); buildPromptEntry(promptsLayout, promptId, userInputDisplays.get(promptId), isLastPrompt); } } private void buildPromptEntry(LinearLayout promptsLayout, String promptId, DisplayUnit displayUnit, boolean isLastPrompt) { Hashtable<String, String> userAnswers = remoteQuerySessionManager.getUserAnswers(); promptsLayout.addView(createPromptMedia(displayUnit)); EditText promptEditText = new EditText(this); if (userAnswers.containsKey(promptId)) { promptEditText.setText(userAnswers.get(promptId)); } promptEditText.setBackgroundResource(R.drawable.login_edit_text); // needed to allow 'done' and 'next' keyboard action promptEditText.setSingleLine(); if (isLastPrompt) { promptEditText.setImeOptions(EditorInfo.IME_ACTION_DONE); } else { // replace 'done' on keyboard with 'next' promptEditText.setImeOptions(EditorInfo.IME_ACTION_NEXT); } promptsLayout.addView(promptEditText); promptsBoxes.put(promptId, promptEditText); } private MediaLayout createPromptMedia(DisplayUnit display) { DisplayData displayData = display.evaluate(); String promptText = Localizer.processArguments(displayData.getName(), new String[]{""}).trim(); TextView text = new TextView(getApplicationContext()); text.setText(promptText); text.setPadding(0, 0, 0, 7); text.setTextColor(Color.BLACK); MediaLayout helpLayout = MediaLayout.buildAudioImageLayout(this, text, displayData.getAudioURI(), displayData.getImageURI()); int padding = (int)getResources().getDimension(R.dimen.help_text_padding); helpLayout.setPadding(padding, padding, padding, padding); return helpLayout; } private void answerPrompts() { remoteQuerySessionManager.clearAnswers(); for (Map.Entry<String, EditText> promptEntry : promptsBoxes.entrySet()) { String promptText = promptEntry.getValue().getText().toString(); if (!"".equals(promptText)) { remoteQuerySessionManager.answerUserPrompt(promptEntry.getKey(), promptText); } } } private void makeQueryRequest() { clearErrorState(); URL url = remoteQuerySessionManager.getBaseUrl(); SimpleHttpTask httpTask; try { httpTask = new SimpleHttpTask(this, url, new HashMap<>(remoteQuerySessionManager.getRawQueryParams()), false, null); } catch (ModernHttpRequester.PlainTextPasswordException e) { enterErrorState(Localization.get("post.not.using.https", url.toString())); return; } httpTask.connect((CommCareTaskConnector)this); httpTask.executeParallel(); } private void clearErrorState() { errorMessage = ""; inErrorState = false; } private void enterErrorState(String message) { errorMessage = message; enterErrorState(); } private void enterErrorState() { inErrorState = true; Log.e(TAG, errorMessage); errorTextView.setText(errorMessage); errorTextView.setVisibility(View.VISIBLE); } private void loadStateFromSavedInstance(Bundle savedInstanceState) { if (savedInstanceState != null) { errorMessage = savedInstanceState.getString(ERROR_MESSAGE_KEY); inErrorState = savedInstanceState.getBoolean(IN_ERROR_STATE_KEY); Hashtable<String, String> answeredPrompts = (Hashtable<String, String>)savedInstanceState.getSerializable(ANSWERED_USER_PROMPTS_KEY); if (answeredPrompts != null) { for (Map.Entry<String, String> entry : answeredPrompts.entrySet()) { remoteQuerySessionManager.answerUserPrompt(entry.getKey(), entry.getValue()); } } } } @Override protected void onSaveInstanceState(Bundle savedInstanceState) { super.onSaveInstanceState(savedInstanceState); answerPrompts(); savedInstanceState.putSerializable(ANSWERED_USER_PROMPTS_KEY, remoteQuerySessionManager.getUserAnswers()); savedInstanceState.putString(ERROR_MESSAGE_KEY, errorMessage); savedInstanceState.putBoolean(IN_ERROR_STATE_KEY, inErrorState); } @Override public void processSuccess(int responseCode, InputStream responseData) { Pair<ExternalDataInstance, String> instanceOrError = remoteQuerySessionManager.buildExternalDataInstance(responseData); if (instanceOrError.first == null) { enterErrorState(Localization.get("query.response.format.error", instanceOrError.second)); } else if (isResponseEmpty(instanceOrError.first)) { Toast.makeText(this, Localization.get("query.response.empty"), Toast.LENGTH_SHORT).show(); } else { CommCareApplication.instance().getCurrentSession().setQueryDatum(instanceOrError.first); setResult(RESULT_OK); finish(); } } private boolean isResponseEmpty(ExternalDataInstance instance) { return !instance.getRoot().hasChildren(); } @Override public void processRedirection(int responseCode) { enterErrorState(Localization.get("post.redirection.error", responseCode + "")); } @Override public void processClientError(int responseCode) { enterErrorState(Localization.get("post.client.error", responseCode + "")); } @Override public void processServerError(int responseCode) { enterErrorState(Localization.get("post.server.error", responseCode + "")); } @Override public void processOther(int responseCode) { enterErrorState(Localization.get("post.unknown.response", responseCode + "")); } @Override public void handleIOException(IOException exception) { enterErrorState(Localization.get("post.io.error", exception.getMessage())); } @Override public CustomProgressDialog generateProgressDialog(int taskId) { String title, message; switch (taskId) { case SimpleHttpTask.SIMPLE_HTTP_TASK_ID: title = Localization.get("query.dialog.title"); message = Localization.get("query.dialog.body"); break; default: Log.w(TAG, "taskId passed to generateProgressDialog does not match " + "any valid possibilities in CommCareHomeActivity"); return null; } return CustomProgressDialog.newInstance(title, message, taskId); } }