/* * Copyright (C) 2012 University of Washington * * 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.odk.collect.android.widgets; import android.app.Activity; import android.content.ActivityNotFoundException; import android.content.Context; import android.content.Intent; import android.graphics.drawable.Drawable; import android.text.method.TextKeyListener; import android.text.method.TextKeyListener.Capitalize; import android.util.Log; import android.util.TypedValue; import android.view.KeyEvent; import android.view.View; import android.view.inputmethod.InputMethodManager; import android.widget.Button; import android.widget.EditText; import android.widget.TableLayout; import android.widget.Toast; import org.javarosa.core.model.data.IAnswerData; import org.javarosa.core.model.data.StringData; import org.javarosa.form.api.FormEntryPrompt; import org.odk.collect.android.R; import org.odk.collect.android.activities.FormEntryActivity; import org.odk.collect.android.application.Collect; import org.odk.collect.android.exception.ExternalParamsException; import org.odk.collect.android.external.ExternalAppsUtils; import java.util.Map; /** * <p>Launch an external app to supply a string value. If the app * does not launch, enable the text area for regular data entry.</p> * * <p>The default button text is "Launch" * * <p>You may override the button text and the error text that is * displayed when the app is missing by using jr:itext() values. * * <p>To use this widget, define an appearance on the <input/> * tag that begins "ex:" and then contains the intent action to lauch. * * <p>e.g., * * <pre> * <input appearance="ex:change.uw.android.TEXTANSWER" ref="/form/passPhrase" > * </pre> * <p>or, to customize the button text and error strings with itext: * <pre> * ... * <bind nodeset="/form/passPhrase" type="string" /> * ... * <itext> * <translation lang="English"> * <text id="textAnswer"> * <value form="short">Text question</value> * <value form="long">Enter your pass phrase</value> * <value form="buttonText">Get Pass Phrase</value> * <value form="noAppErrorString">Pass Phrase Tool is not installed! * Please proceed to manually enter pass phrase.</value> * </text> * </translation> * </itext> * ... * <input appearance="ex:change.uw.android.TEXTANSWER" ref="/form/passPhrase"> * <label ref="jr:itext('textAnswer')"/> * </input> * </pre> * * @author mitchellsundt@gmail.com * */ public class ExStringWidget extends QuestionWidget implements IBinaryWidget { private final String t = getClass().getName(); private boolean mHasExApp = true; private Button mLaunchIntentButton; private Drawable mTextBackground; protected EditText mAnswer; public ExStringWidget(Context context, FormEntryPrompt prompt) { super(context, prompt); TableLayout.LayoutParams params = new TableLayout.LayoutParams(); params.setMargins(7, 5, 7, 5); // set text formatting mAnswer = new EditText(context); mAnswer.setId(QuestionWidget.newUniqueId()); mAnswer.setTextSize(TypedValue.COMPLEX_UNIT_DIP, mAnswerFontsize); mAnswer.setLayoutParams(params); mTextBackground = mAnswer.getBackground(); mAnswer.setBackgroundDrawable(null); // capitalize nothing mAnswer.setKeyListener(new TextKeyListener(Capitalize.NONE, false)); // needed to make long read only text scroll mAnswer.setHorizontallyScrolling(false); mAnswer.setSingleLine(false); String s = prompt.getAnswerText(); if (s != null) { mAnswer.setText(s); } if (mPrompt.isReadOnly()) { mAnswer.setBackgroundDrawable(null); } if (mPrompt.isReadOnly() || mHasExApp) { mAnswer.setFocusable(false); mAnswer.setClickable(false); } String appearance = prompt.getAppearanceHint(); String[] attrs = appearance.split(":"); final String intentName = ExternalAppsUtils.extractIntentName(attrs[1]); final Map<String, String> exParams = ExternalAppsUtils.extractParameters(attrs[1]); final String buttonText; final String errorString; String v = mPrompt.getSpecialFormQuestionText("buttonText"); buttonText = (v != null) ? v : context.getString(R.string.launch_app); v = mPrompt.getSpecialFormQuestionText("noAppErrorString"); errorString = (v != null) ? v : context.getString(R.string.no_app); // set button formatting mLaunchIntentButton = new Button(getContext()); mLaunchIntentButton.setId(QuestionWidget.newUniqueId()); mLaunchIntentButton.setText(buttonText); mLaunchIntentButton.setTextSize(TypedValue.COMPLEX_UNIT_DIP, mAnswerFontsize); mLaunchIntentButton.setPadding(20, 20, 20, 20); mLaunchIntentButton.setEnabled(!mPrompt.isReadOnly()); mLaunchIntentButton.setLayoutParams(params); mLaunchIntentButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Intent i = new Intent(intentName); try { ExternalAppsUtils.populateParameters(i, exParams, mPrompt.getIndex().getReference()); Collect.getInstance().getFormController().setIndexWaitingForData(mPrompt.getIndex()); fireActivity(i); } catch (ExternalParamsException e) { Log.e(t, e.getMessage(), e); onException(e.getMessage()); } catch (ActivityNotFoundException e) { Log.e(t, e.getMessage(), e); onException(errorString); } } private void onException(String toastText) { mHasExApp = false; if ( !mPrompt.isReadOnly() ) { mAnswer.setBackgroundDrawable(mTextBackground); mAnswer.setFocusable(true); mAnswer.setFocusableInTouchMode(true); mAnswer.setClickable(true); } mLaunchIntentButton.setEnabled(false); mLaunchIntentButton.setFocusable(false); Collect.getInstance().getFormController().setIndexWaitingForData(null); Toast.makeText(getContext(), toastText, Toast.LENGTH_SHORT) .show(); ExStringWidget.this.mAnswer.requestFocus(); } }); // finish complex layout addView(mLaunchIntentButton); addView(mAnswer); } protected void fireActivity(Intent i) throws ActivityNotFoundException { i.putExtra("value", mPrompt.getAnswerText()); Collect.getInstance().getActivityLogger().logInstanceAction(this, "launchIntent", i.getAction(), mPrompt.getIndex()); ((Activity) getContext()).startActivityForResult(i, FormEntryActivity.EX_STRING_CAPTURE); } @Override public void clearAnswer() { mAnswer.setText(null); } @Override public IAnswerData getAnswer() { String s = mAnswer.getText().toString(); if (s == null || s.equals("")) { return null; } else { return new StringData(s); } } /** * Allows answer to be set externally in {@Link FormEntryActivity}. */ @Override public void setBinaryData(Object answer) { StringData stringData = ExternalAppsUtils.asStringData(answer); mAnswer.setText(stringData == null ? null : stringData.getValue().toString()); Collect.getInstance().getFormController().setIndexWaitingForData(null); } @Override public void setFocus(Context context) { // Put focus on text input field and display soft keyboard if appropriate. InputMethodManager inputManager = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE); if ( mHasExApp ) { // hide keyboard inputManager.hideSoftInputFromWindow(mAnswer.getWindowToken(), 0); // focus on launch button mLaunchIntentButton.requestFocus(); } else { if (!mPrompt.isReadOnly()) { mAnswer.requestFocus(); inputManager.showSoftInput(mAnswer, 0); /* * If you do a multi-question screen after a "add another group" dialog, this won't * automatically pop up. It's an Android issue. * * That is, if I have an edit text in an activity, and pop a dialog, and in that * dialog's button's OnClick() I call edittext.requestFocus() and * showSoftInput(edittext, 0), showSoftinput() returns false. However, if the edittext * is focused before the dialog pops up, everything works fine. great. */ } else { inputManager.hideSoftInputFromWindow(mAnswer.getWindowToken(), 0); } } } @Override public boolean isWaitingForBinaryData() { return mPrompt.getIndex().equals(Collect.getInstance().getFormController().getIndexWaitingForData()); } @Override public void cancelWaitingForBinaryData() { Collect.getInstance().getFormController().setIndexWaitingForData(null); } @Override public boolean onKeyDown(int keyCode, KeyEvent event) { if (event.isAltPressed() == true) { return false; } return super.onKeyDown(keyCode, event); } @Override public void setOnLongClickListener(OnLongClickListener l) { mAnswer.setOnLongClickListener(l); mLaunchIntentButton.setOnLongClickListener(l); } @Override public void cancelLongPress() { super.cancelLongPress(); mAnswer.cancelLongPress(); mLaunchIntentButton.cancelLongPress(); } }