package org.sana.android.procedure; import java.net.URISyntaxException; import org.sana.R; import org.sana.android.activity.ProcedureRunner; import org.sana.android.content.core.ObservationWrapper; import org.sana.android.db.BinaryDAO; import org.sana.android.db.ModelWrapper; import org.sana.android.db.SanaDB.BinarySQLFormat; import org.sana.android.provider.Observations; import org.sana.android.service.PluginService; import org.w3c.dom.Node; import android.app.Activity; import android.content.ContentUris; import android.content.Context; import android.content.Intent; import android.net.Uri; import android.os.Bundle; import android.text.TextUtils; import android.util.Log; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup.LayoutParams; import android.widget.Button; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.Toast; /** * An Element within a Procedure which launches a plug-in, i.e. third * party application for data capture as an observation. * <br/> * <b>Note:<b> The Activity information can usually be obtained by launching the * application and viewing the log cat output as in: * * <code>I/ActivityManager: Starting activity: Intent { * act=android.media.action.VIDEO_CAPTURE * cmp=com.google.android.camera/com.android.camera.VideoCamera }</code> * <br/> * where the xml attributes for the element would be set as follows: * <ul type="none"> * <li><code>action="android.media.action.VIDEO_CAPTURE"</code></li> * <li><code>mimeType=""</code></li> * </ul> * The <code>act</code> correlates to the <code>name</code> attribute listed * in an intent filter for the Activity in it's application manifest. * The <code>mimeType</code> will appear as <code>typ=</code> in the String * representation of the Intent if available. * <p/> * <b>Collects: </b>Determined by plug-in activity but will return either a * resource identifier or string. * * @author Sana Development Team * */ public class PluginElement extends ProcedureElement implements OnClickListener { private static final String TAG = PluginElement.class.getSimpleName(); public static final String PARAMS_NAME = "keys"; public static final String DELIMITER = ";"; // the plugin action -> intent action string protected final String pluginAction; // the plugin package -> Activity package protected final String mimeType; // launch button for for starting Activity with GET_CONTENT intent protected Button mCaptureButton; // launch button for for starting Activity with ACTION_VIEW intent protected Button mViewButton; protected ImageView mPluginIcon; protected String mLabel = null; protected Bundle mParams; /** * Constructs a new PluginElement * * @param id The value of the "id" attribute * @param question The value of the "question" attribute * @param answer The value of the "answer" attribute * @param concept The value of the "concept" attribute * @param figure The value of the "figure" attribute * @param audio The value of the "audio" attribute * @param pluginAction The value of the "action" attribute. This is the * intent-filter action string from the plugin activity. * @param mimeType The content type which the plug-in will collect. Used to * construct a launch intent. */ protected PluginElement(String id, String question, String answer, String concept, String figure, String audioPrompt, String action, String mimeType) { this(id, question, answer, concept, figure, audioPrompt, action, new Bundle(), mimeType); } protected PluginElement(String id, String question, String answer, String concept, String figure, String audioPrompt, String action, Bundle params, String mimeType){ super(id, question, answer, concept, figure, audioPrompt); this.pluginAction = action; this.mimeType = mimeType; setControlParams(params); } /** * Fetches the plug-in String for this element * @return the action string */ public String getAction(){ return pluginAction; } /** * Fetches the plug-in String for this element * @return the action string */ public String getMimeType(){ return mimeType; } public String getControlString(){ StringBuilder control = new StringBuilder(); control.append(getAction()); for(String key: mParams.keySet()){ control.append(DELIMITER); control.append(key + "=" + mParams.getString(key)); } return control.toString(); } /** * Gets the Intent which can be used to launch the plug-in activity through * ProcedureRunner * @throws URISyntaxException */ protected Intent getRawPluginIntent() { Intent pi = new Intent(); if(!TextUtils.isEmpty(pluginAction)){ pi = new Intent(pluginAction); } if(!TextUtils.isEmpty(mimeType)) pi.setType(mimeType); Log.d(TAG, "Raw plugin intent: " + pi.toUri(Intent.URI_INTENT_SCHEME)); return pi; } /** * Gets the Intent which can be used to launch the plug-in activity through * ProcedureRunner * @throws URISyntaxException */ protected Intent getViewIntent() { Log.d(TAG, "view: " + answer); Uri ue = getProcedure().getInstanceUri(); String encounter = ModelWrapper.getUuid(ue, getContext().getContentResolver()); Intent intent = null; if(TextUtils.isEmpty(answer)) return intent; try{ intent = new Intent(); //Uri obs = Uri.parse(answer); Uri obs = ObservationWrapper.getReferenceByEncounterAndId( getContext().getContentResolver(), encounter, id); intent = new Intent(getAction() +"_VIEW"); if(obs != null && !obs.equals(Observations.CONTENT_URI)){ intent.setDataAndType(obs,mimeType) .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); } Log.d(TAG, "....encounter" + encounter); Log.d(TAG, "....obs" + obs); Log.d(TAG, "intent: " + intent); } catch (Exception e){ e.printStackTrace(); } return intent; } /** * Handles clicks on the button responsible for triggering the launch of the * plugin activity */ @Override public void onClick(View v) { if (v == mCaptureButton) { capture(); } else if(v == mViewButton){ view(); } } /** * Launches an Intent to capture data captured using this element's plug-in. */ protected void capture(){ try { Uri obs = Uri.withAppendedPath(getProcedure().getInstanceUri(),id); Intent plugin = new Intent(getAction()); plugin.putExtras(getControlParams()); plugin.putExtra(Observations.Contract.ID, id); plugin.putExtra(Observations.Contract.CONCEPT, concept); Log.d(TAG, "obs: " + obs); Intent i = new Intent(getContext(), ProcedureRunner.class); i.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP) .putExtra(ProcedureRunner.INTENT_KEY_STRING, ProcedureRunner.PLUGIN_INTENT_REQUEST_CODE) .putExtra(Observations.Contract.ID, id) .putExtra(Intent.EXTRA_INTENT, plugin) .setDataAndType(getProcedure().getInstanceUri(),mimeType); ((Activity) getContext()).startActivity(i); } catch (Exception e){ Log.e(TAG, "Error starting plugin: " + e.toString()); } } /** * Launches an Intent to view the data captured as this element's answer. */ protected void view(){ try { Intent intent = getViewIntent(); if(intent != null && intent.getData() != null){ getContext().startActivity(intent); } else { Toast.makeText(getContext(), getContext().getString(R.string.msg_err_no_answer), Toast.LENGTH_SHORT).show(); } } catch (Exception e){ Log.e(TAG, "Empty answer of no media found: " + e.toString()); } } protected Bundle getControlParams(){ Bundle params = new Bundle(mParams); return params; } protected String getControlParam(String name){ return mParams.getString(name); } protected void setControlParams(Bundle params){ mParams = new Bundle(params); } protected void updateControlParams(Bundle params){ mParams.putAll(params); } protected void setControlParam(String name, String value){ mParams.putString(name, value); } @Override protected void appendOptionalAttributes(StringBuilder sb){ sb.append("\" action=\"" + getControlString()); sb.append("\" mimeType=\"" + getMimeType()); } /** @see ProcedureElement#fromXML(String, String, String, String, String, String, Node) */ public static PluginElement fromXML(String id, String question, String answer, String concept, String figure, String audio, Node node) throws ProcedureParseException { String controlStr = node.getAttributes().getNamedItem("action") .getNodeValue(); if(TextUtils.isEmpty(controlStr)) throw new ProcedureParseException("Invalid contol string: NULL"); String[] control = controlStr.split(DELIMITER); String action = control[0]; Bundle params = new Bundle(); for(int i = 1; i < control.length; i++){ String[] param = control[i].split("="); params.putString(param[0], param[1]); } String mimeType = node.getAttributes().getNamedItem("mimeType") .getNodeValue(); return new PluginElement(id, question, answer, concept, figure, audio, action,params,mimeType); } /** {@inheritDoc} */ @Override protected View createView(Context c) { Log.d(TAG, ""); // New Layout LinearLayout container = new LinearLayout(c); container.setOrientation(LinearLayout.VERTICAL); container.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT)); // Plugin Launcher View plug = getContentView(c); container.addView(plug, new LinearLayout.LayoutParams( LayoutParams.FILL_PARENT,LayoutParams.WRAP_CONTENT,0.1f)); //Add data viewer View review = viewDataView(c); container.addView(review, new LinearLayout.LayoutParams( LayoutParams.FILL_PARENT,LayoutParams.WRAP_CONTENT,0.1f)); container.setWeightSum(1.0f); return encapsulateQuestion(c, container); } protected View getContentView(Context c){ //Add Capture button mCaptureButton = new Button(c); mCaptureButton.setText(c.getString(R.string.general_capture_data)); mCaptureButton.setOnClickListener(this); return mCaptureButton; } protected View viewDataView(Context c){ mViewButton = new Button(c); mViewButton.setText(c.getString(R.string.general_view_data)); mViewButton.setOnClickListener(this); return mViewButton; } /** {@inheritDoc} */ @Override public ElementType getType() { return ProcedureElement.ElementType.PLUGIN; } /** {@inheritDoc} */ @Override public String getAnswer() { Log.i(TAG, "getAnswer()"); Log.d(TAG, "... id "+id + ", answer:" + answer); return answer; } /** Sets the answer String for this element */ @Override public void setAnswer(String answer) { Log.i(TAG, "setAnswer()"); Log.d(TAG, "... set answer:" + answer); this.answer = answer; } }