/******************************************************************************* * 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.Dialog; import android.content.Context; import android.content.Intent; import android.database.Cursor; import android.graphics.Bitmap; import android.graphics.drawable.BitmapDrawable; import android.net.Uri; import android.os.Bundle; import android.support.v4.app.ListFragment; import android.support.v4.app.LoaderManager; import android.support.v4.content.CursorLoader; import android.support.v4.content.Loader; import android.support.v4.widget.SimpleCursorAdapter; import android.support.v4.widget.SimpleCursorAdapter.ViewBinder; import android.text.Html; import android.text.format.DateUtils; import android.view.LayoutInflater; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; import android.widget.ImageView; import android.widget.TextView; import android.widget.Toast; import com.google.android.imageloader.ImageLoader; import com.google.android.imageloader.ImageLoader.BindResult; import com.google.android.imageloader.ImageLoader.Callback; import org.json.JSONArray; import org.json.JSONException; import org.ohmage.ConfigHelper; import org.ohmage.OhmageApi; import org.ohmage.OhmageApplication; import org.ohmage.OhmageMarkdown; import org.ohmage.R; import org.ohmage.Utilities; import org.ohmage.db.DbContract; import org.ohmage.db.DbContract.Campaigns; import org.ohmage.db.DbContract.PromptResponses; import org.ohmage.db.DbContract.Responses; import org.ohmage.db.DbContract.SurveyPrompts; import org.ohmage.db.DbContract.Surveys; import org.ohmage.db.Models.Response; import org.ohmage.logprobe.Analytics; import org.ohmage.logprobe.Log; import org.ohmage.prompt.AbstractPrompt; import org.ohmage.service.SurveyGeotagService; import org.ohmage.ui.BaseInfoActivity; import org.ohmage.ui.ResponseActivityHelper; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; /** * This Activity is used to display Information for an individual response. It * is called with {@link Intent#ACTION_VIEW} on the URI specified by * {@link DbContract.Responses#getResponseUri(long)} * * @author cketcham * */ public class ResponseInfoActivity extends BaseInfoActivity implements LoaderManager.LoaderCallbacks<Cursor> { private ImageLoader mImageLoader; private TextView mapViewButton; private TextView uploadButton; private ResponseActivityHelper mResponseHelper; private int mStatus; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentFragment(new ResponsePromptsFragment()); mResponseHelper = new ResponseActivityHelper(this); mImageLoader = ImageLoader.get(this); // inflate the campaign-specific info page into the scrolling framelayout LayoutInflater inflater = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE); // and inflate all the possible commands into the button tray inflater.inflate(R.layout.response_info_buttons, mButtonTray, true); // Prepare the loader. Either re-connect with an existing one, // or start a new one. getSupportLoaderManager().initLoader(0, null, this); mapViewButton = (TextView) findViewById(R.id.response_info_button_view_map); mapViewButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { Analytics.widget(v); startActivity(new Intent(OhmageApplication.VIEW_MAP, getIntent().getData())); } }); uploadButton = (TextView) findViewById(R.id.response_info_button_upload); uploadButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { Analytics.widget(v); if(mStatus == Response.STATUS_STANDBY) mResponseHelper.queueForUpload(getIntent().getData()); else { Bundle bundle = new Bundle(); bundle.putParcelable(ResponseActivityHelper.KEY_URI, getIntent().getData()); showDialog(mStatus, bundle); } } }); } @Override public void onContentChanged() { super.onContentChanged(); mEntityHeader.setVisibility(View.GONE); } @Override protected void onPrepareDialog(int id, Dialog dialog, Bundle args) { mResponseHelper.onPrepareDialog(id, dialog, args); } @Override protected Dialog onCreateDialog(int id, Bundle args) { return mResponseHelper.onCreateDialog(id, args); } private interface ResponseQuery { String[] PROJECTION = { Campaigns.CAMPAIGN_NAME, Surveys.SURVEY_TITLE, Responses.RESPONSE_TIME, Campaigns.CAMPAIGN_ICON, Responses.RESPONSE_LOCATION_STATUS, Responses.RESPONSE_STATUS}; int CAMPAIGN_NAME = 0; int SURVEY_TITLE = 1; int TIME = 2; int CAMPAIGN_ICON = 3; int LOCATION_STATUS = 4; int STATUS = 5; } @Override public Loader<Cursor> onCreateLoader(int id, Bundle bundle) { return new CursorLoader(this, getIntent().getData(), ResponseQuery.PROJECTION, null, null, null); } @Override public void onLoadFinished(Loader<Cursor> loader, Cursor data) { if (!data.moveToFirst()) { Toast.makeText(this, R.string.response_info_response_deleted, Toast.LENGTH_SHORT).show(); finish(); return; } final String surveyName = data.getString(ResponseQuery.SURVEY_TITLE); final Long completedDate = data.getLong(ResponseQuery.TIME); mStatus = data.getInt(ResponseQuery.STATUS); if(mStatus == Response.STATUS_STANDBY) uploadButton.setContentDescription(getString(R.string.response_info_entity_action_button_upload_description)); else if(mStatus == Response.STATUS_WAITING_FOR_LOCATION) uploadButton.setContentDescription(getString(R.string.response_info_entity_action_button_upload_force_description)); else uploadButton.setContentDescription(getString(R.string.response_info_entity_action_button_upload_error_description)); mHeadertext.setText(surveyName); mSubtext.setText(data.getString(ResponseQuery.CAMPAIGN_NAME)); // If we aren't in single campaign mode, show the campaign name mSubtext.setVisibility((ConfigHelper.isSingleCampaignMode()) ? View.GONE : View.VISIBLE); SimpleDateFormat df = new SimpleDateFormat(); mNotetext.setText(df.format(new Date(completedDate))); final String iconUrl = data.getString(ResponseQuery.CAMPAIGN_ICON); if(iconUrl == null || mImageLoader.bind(mIconView, iconUrl, null) != ImageLoader.BindResult.OK) { mIconView.setImageResource(R.drawable.apple_logo); } mEntityHeader.setVisibility(View.VISIBLE); // Make the map view button status aware so it can provide some useful info about the gps state if(mStatus == Response.STATUS_WAITING_FOR_LOCATION) { mapViewButton.setText(R.string.response_info_gps_wait); mapViewButton.setEnabled(false); } else if(!(SurveyGeotagService.LOCATION_VALID.equals(data.getString(ResponseQuery.LOCATION_STATUS)))) { mapViewButton.setText(R.string.response_info_no_location); mapViewButton.setEnabled(false); } else { mapViewButton.setText(R.string.response_info_view_map); mapViewButton.setEnabled(true); } // Make upload button visible if applicable uploadButton.setVisibility(View.VISIBLE); switch(mStatus) { case Response.STATUS_DOWNLOADED: case Response.STATUS_UPLOADED: uploadButton.setVisibility(View.GONE); break; case Response.STATUS_STANDBY: case Response.STATUS_WAITING_FOR_LOCATION: uploadButton.setText(R.string.response_info_upload); uploadButton.setEnabled(true); break; case Response.STATUS_QUEUED: case Response.STATUS_UPLOADING: uploadButton.setText(R.string.response_info_uploading); uploadButton.setEnabled(false); break; default: // Error uploadButton.setText(R.string.response_info_upload_error); uploadButton.setEnabled(true); } } @Override public void onLoaderReset(Loader<Cursor> loader) { mEntityHeader.setVisibility(View.GONE); } public static class ResponsePromptsFragment extends ListFragment implements LoaderManager.LoaderCallbacks<Cursor> { private static final String TAG = "ResponseInfoListFragment"; // This is the Adapter being used to display the list's data. PromptResponsesAdapter mAdapter; @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); // We have no menu items to show in action bar. setHasOptionsMenu(false); getListView().setDivider(null); // Create an empty adapter we will use to display the loaded data. mAdapter = new PromptResponsesAdapter(getActivity(), null, new String[] { SurveyPrompts.SURVEY_PROMPT_TEXT, PromptResponses.PROMPT_RESPONSE_VALUE }, new int[] { android.R.id.text1, R.id.prompt_value }, 0, getResponseId()); setListAdapter(mAdapter); // Start out with a progress indicator. setListShown(false); getLoaderManager().initLoader(0, null, this); } @Override public Loader<Cursor> onCreateLoader(int id, Bundle bundle) { return new CursorLoader(getActivity(), Responses.buildPromptResponsesUri(Long.valueOf(getResponseId())), null, PromptResponses.PROMPT_RESPONSE_VALUE + " !=?", new String[] { "NOT_DISPLAYED" }, null); } public String getResponseId() { return Responses.getResponseId(getActivity().getIntent().getData()); } @Override public void onLoadFinished(Loader<Cursor> loader, Cursor data) { mAdapter.swapCursor(data); // The list should now be shown. if (isResumed()) { setListShown(true); } else { setListShownNoAnimation(true); } } @Override public void onLoaderReset(Loader<Cursor> loader) { mAdapter.swapCursor(null); } private static class PromptResponsesAdapter extends SimpleCursorAdapter implements ViewBinder { public static final int UNKNOWN_RESPONSE = -1; public static final int TEXT_RESPONSE = 0; public static final int IMAGE_RESPONSE = 1; public static final int HOURSBEFORENOW_RESPONSE = 2; public static final int TIMESTAMP_RESPONSE = 3; public static final int MULTICHOICE_RESPONSE = 4; public static final int MULTICHOICE_CUSTOM_RESPONSE = 5; public static final int SINGLECHOICE_RESPONSE = 6; public static final int SINGLECHOICE_CUSTOM_RESPONSE = 7; public static final int NUMBER_RESPONSE = 8; public static final int REMOTE_RESPONSE = 9; public static final int VIDEO_RESPONSE = 10; private final String mResponseId; private final ImageLoader mImageLoader; public PromptResponsesAdapter(Context context, Cursor c, String[] from, int[] to, int flags, String responseId) { super(context, R.layout.response_prompt_list_item, c, from, to, flags); mImageLoader = ImageLoader.get(context); setViewBinder(this); mResponseId = responseId; } @Override public int getItemViewType(int position) { if(getCursor().moveToPosition(position)) return getItemViewType(getCursor()); return 0; } /** * Gets the view type for this item by the cursor. The cursor needs to be moved to the correct position prior to calling * this function * @param cursor * @return the view type */ public int getItemViewType(Cursor cursor) { String promptType = getItemPromptType(cursor); if("photo".equals(promptType)) return IMAGE_RESPONSE; else if ("video".equals(promptType)) return VIDEO_RESPONSE; else if ("text".equals(promptType)) return TEXT_RESPONSE; else if ("hours_before_now".equals(promptType)) return HOURSBEFORENOW_RESPONSE; else if ("timestamp".equals(promptType)) return TIMESTAMP_RESPONSE; else if ("single_choice".equals(promptType)) return SINGLECHOICE_RESPONSE; else if ("single_choice_custom".equals(promptType)) return SINGLECHOICE_CUSTOM_RESPONSE; else if ("multi_choice".equals(promptType)) return MULTICHOICE_RESPONSE; else if ("multi_choice_custom".equals(promptType)) return MULTICHOICE_CUSTOM_RESPONSE; else if ("number".equals(promptType)) return NUMBER_RESPONSE; else if ("remote_activity".equals(promptType)) return REMOTE_RESPONSE; else return UNKNOWN_RESPONSE; } /** * Gets the prompt type for this item by the cursor. The cursor needs to be moved to the correct position prior to calling * this function * @param cursor * @return */ public String getItemPromptType(Cursor cursor) { return cursor.getString(cursor.getColumnIndex(SurveyPrompts.SURVEY_PROMPT_TYPE)); } @Override public View newView(Context context, Cursor cursor, ViewGroup parent) { View view = super.newView(context, cursor, parent); View image = view.findViewById(R.id.prompt_image_value); View progress = view.findViewById(R.id.prompt_image_progress); View text = view.findViewById(R.id.prompt_text_value); View value = view.findViewById(R.id.prompt_value); ImageView icon = (ImageView) view.findViewById(R.id.prompt_icon); int itemViewType = getItemViewType(cursor); // set the icon for each prompt type switch(itemViewType) { case VIDEO_RESPONSE: case IMAGE_RESPONSE: icon.setImageResource(R.drawable.prompttype_photo); break; case HOURSBEFORENOW_RESPONSE: icon.setImageResource(R.drawable.prompttype_hoursbeforenow); break; case TIMESTAMP_RESPONSE: icon.setImageResource(R.drawable.prompttype_timestamp); break; case MULTICHOICE_RESPONSE: icon.setImageResource(R.drawable.prompttype_multichoice); break; case MULTICHOICE_CUSTOM_RESPONSE: icon.setImageResource(R.drawable.prompttype_multichoice_custom); break; case SINGLECHOICE_RESPONSE: icon.setImageResource(R.drawable.prompttype_singlechoice); break; case SINGLECHOICE_CUSTOM_RESPONSE: icon.setImageResource(R.drawable.prompttype_singlechoice_custom); break; case NUMBER_RESPONSE: icon.setImageResource(R.drawable.prompttype_number); break; case REMOTE_RESPONSE: icon.setImageResource(R.drawable.prompttype_remote); break; case TEXT_RESPONSE: icon.setImageResource(R.drawable.prompttype_text); break; } // now set up how they're actually displayed // there are only two categories: image and non-image (i.e. text) if (itemViewType != IMAGE_RESPONSE // also if the image was skipped we are showing the text view || AbstractPrompt.SKIPPED_VALUE.equals(cursor.getString(cursor.getColumnIndex(PromptResponses.PROMPT_RESPONSE_VALUE)))) { progress.setVisibility(View.GONE); image.setVisibility(View.GONE); text.setVisibility(View.VISIBLE); value.setTag(text); } else { progress.setVisibility(View.VISIBLE); image.setVisibility(View.VISIBLE); text.setVisibility(View.GONE); value.setTag(image); value.setBackgroundResource(R.drawable.prompt_response_image_item_bg); } return view; } @Override public int getViewTypeCount() { return 11; } @Override public boolean areAllItemsEnabled() { return false; } @Override public boolean isEnabled(int position) { return false; } @Override public boolean setViewValue(View view, Cursor cursor, int columnIndex) { if(cursor.getColumnName(columnIndex).equals(PromptResponses.PROMPT_RESPONSE_VALUE)) { final String value = cursor.getString(columnIndex); if(view.getTag() instanceof ImageView) { String campaignUrn = cursor.getString(cursor.getColumnIndex(Responses.CAMPAIGN_URN)); final File file = Response.getTemporaryResponsesMedia(value); final ImageView imageView = (ImageView) view.getTag(); if(file != null && file.exists()) { try { BitmapDrawable d = (BitmapDrawable) imageView.getDrawable(); if(d!=null) d.getBitmap().recycle(); Bitmap img = Utilities.decodeImage(file, 600); imageView.setImageBitmap(img); imageView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Analytics.widget(v, "View Local Fullsize Image"); Intent intent = new Intent(Intent.ACTION_VIEW); intent.setDataAndType(Uri.fromFile(file), "image/jpeg"); mContext.startActivity(intent); } }); return true; } catch (FileNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { Log.e(TAG, "Error decoding image", e); } } String url = OhmageApi.defaultImageReadUrl(value, campaignUrn, "small"); final String largeUrl = OhmageApi.defaultImageReadUrl(value, campaignUrn, null); imageView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Analytics.widget(v, "View Fullsize Image"); Intent intent = new Intent(OhmageApplication.ACTION_VIEW_REMOTE_IMAGE, Uri.parse(largeUrl)); mContext.startActivity(intent); } }); mImageLoader.clearErrors(); BindResult bindResult = mImageLoader.bind((ImageView)view.getTag(), url, new Callback() { @Override public void onImageLoaded(ImageView view, String url) { imageView.setVisibility(View.VISIBLE); imageView.setClickable(true); imageView.setFocusable(true); } @Override public void onImageError(ImageView view, String url, Throwable error) { imageView.setVisibility(View.VISIBLE); imageView.setImageResource(android.R.drawable.ic_dialog_alert); imageView.setClickable(false); imageView.setFocusable(false); } }); if(bindResult == ImageLoader.BindResult.ERROR) { imageView.setImageResource(android.R.drawable.ic_dialog_alert); imageView.setClickable(false); imageView.setFocusable(false); } else if(bindResult == ImageLoader.BindResult.LOADING){ imageView.setVisibility(View.GONE); } } else if(view.getTag() instanceof TextView) { String prompt_type = getItemPromptType(cursor); if("multi_choice_custom".equals(prompt_type) || "multi_choice".equals(prompt_type)) { try { JSONArray choices = new JSONArray(value); StringBuilder builder = new StringBuilder(); for(int i=0;i<choices.length();i++) { if(i != 0) builder.append("<br\\>"); builder.append("• "); builder.append(OhmageMarkdown.parseHtml(choices.get(i).toString())); } ((TextView) view.getTag()).setText(Html.fromHtml(builder.toString())); return true; } catch (JSONException e) { // TODO Auto-generated catch block e.printStackTrace(); } } else if("single_choice_custom".equals(prompt_type) || "single_choice".equals(prompt_type)) { ((TextView) view.getTag()).setText(OhmageMarkdown.parse(value)); return true; } else if("timestamp".equals(prompt_type)) { SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); try { long time = format.parse(value).getTime(); StringBuilder timeDisplay = new StringBuilder(DateUtils.formatDateTime(mContext, time, DateUtils.FORMAT_SHOW_YEAR)); timeDisplay.append(" at "); timeDisplay.append(DateUtils.formatDateTime(mContext, time, DateUtils.FORMAT_SHOW_TIME)); ((TextView) view.getTag()).setText(timeDisplay); return true; } catch (ParseException e) { // TODO Auto-generated catch block e.printStackTrace(); } } ((TextView) view.getTag()).setText(value); } return true; } return false; } } } }