/*
* Copyright (C) 2010-2016 Stichting Akvo (Akvo Foundation)
*
* This file is part of Akvo Flow.
*
* Akvo Flow is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Akvo Flow is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Akvo Flow. If not, see <http://www.gnu.org/licenses/>.
*/
package org.akvo.flow.ui.view;
import android.app.Dialog;
import android.content.Context;
import android.content.Intent;
import android.media.ThumbnailUtils;
import android.net.Uri;
import android.os.Bundle;
import android.provider.MediaStore;
import android.text.TextUtils;
import android.view.ContextThemeWrapper;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.Window;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
import org.akvo.flow.R;
import org.akvo.flow.async.MediaSyncTask;
import org.akvo.flow.domain.Question;
import org.akvo.flow.domain.QuestionResponse;
import org.akvo.flow.domain.response.value.Location;
import org.akvo.flow.domain.response.value.Media;
import org.akvo.flow.event.QuestionInteractionEvent;
import org.akvo.flow.event.SurveyListener;
import org.akvo.flow.event.TimedLocationListener;
import org.akvo.flow.serialization.response.value.MediaValue;
import org.akvo.flow.util.ConstantUtil;
import org.akvo.flow.util.FileUtil;
import org.akvo.flow.util.ImageUtil;
import java.io.File;
/**
* Question type that supports taking a picture/video/audio recording with the
* device's on-board camera.
*
* @author Christopher Fagiani
*/
public class MediaQuestionView extends QuestionView implements OnClickListener,
TimedLocationListener.Listener, MediaSyncTask.DownloadListener {
private Button mMediaButton;
private ImageView mImageView;
private ProgressBar mProgressBar;
private View mDownloadBtn;
private TextView mLocationInfo;
private String mMediaType;
private TimedLocationListener mLocationListener;
private Media mMedia;
public MediaQuestionView(Context context, Question q, SurveyListener surveyListener,
String type) {
super(context, q, surveyListener);
mMediaType = type;
mLocationListener = new TimedLocationListener(context, this, !q.isLocked());
init();
}
private void init() {
setQuestionView(R.layout.media_question_view);
mMediaButton = (Button)findViewById(R.id.media_btn);
mImageView = (ImageView)findViewById(R.id.image);
mProgressBar = (ProgressBar)findViewById(R.id.progress);
mDownloadBtn = findViewById(R.id.download);
mLocationInfo = (TextView)findViewById(R.id.location_info);
if (isImage()) {
mMediaButton.setText(R.string.takephoto);
} else {
mMediaButton.setText(R.string.takevideo);
}
mMediaButton.setOnClickListener(this);
if (isReadOnly()) {
mMediaButton.setEnabled(false);
}
mImageView.setOnClickListener(this);
mDownloadBtn.setOnClickListener(this);
mMedia = null;
hideDownloadOptions();
}
private void hideDownloadOptions() {
mProgressBar.setVisibility(View.GONE);
mDownloadBtn.setVisibility(View.GONE);
}
/**
* handle the action button click
*/
public void onClick(View v) {
// TODO: Use switch instead of if-else
if (v == mImageView) {
String filename = mMedia != null ? mMedia.getFilename() : null;
if (TextUtils.isEmpty(filename) || !(new File(filename).exists())) {
Toast.makeText(getContext(), R.string.error_img_preview, Toast.LENGTH_SHORT).show();
return;
}
if (isImage()) {
// Images are embedded in the app itself, whereas video are delegated through an Intent
Dialog dia = new Dialog(new ContextThemeWrapper(getContext(), R.style.Flow_Dialog));
dia.requestWindowFeature(Window.FEATURE_NO_TITLE);
ImageView imageView = new ImageView(getContext());
imageView.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT,
LayoutParams.MATCH_PARENT));
ImageUtil.displayImage(imageView, filename);
dia.setContentView(imageView);
dia.show();
} else {
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(Uri.fromFile(new File(filename)), "video/mp4");
getContext().startActivity(intent);
}
} else if (v == mMediaButton) {
if (isImage()) {
notifyQuestionListeners(QuestionInteractionEvent.TAKE_PHOTO_EVENT);
} else {
notifyQuestionListeners(QuestionInteractionEvent.TAKE_VIDEO_EVENT);
}
} else if (v == mDownloadBtn) {
mDownloadBtn.setVisibility(GONE);
mProgressBar.setVisibility(VISIBLE);
MediaSyncTask downloadTask = new MediaSyncTask(getContext(), new File(mMedia.getFilename()), this);
downloadTask.execute();
}
}
/**
* display the completion icon and install the response in the question
* object
*/
@Override
public void questionComplete(Bundle mediaData) {
String result = mediaData != null ? mediaData.getString(ConstantUtil.MEDIA_FILE_KEY) : null;
if (result != null) {
mMedia = new Media();
mMedia.setFilename(result);
captureResponse();
displayThumbnail();
if (isImage()) {
if (ImageUtil.getLocation(result) == null) {
mLocationListener.start();
}
displayLocationInfo();
}
}
}
/**
* restores the file path for the file and turns on the complete icon if the
* file exists
*/
@Override
public void rehydrate(QuestionResponse resp) {
super.rehydrate(resp);
if (resp == null || TextUtils.isEmpty(resp.getValue())) {
return;
}
mMedia = MediaValue.deserialize(resp.getValue());
displayThumbnail();
String filename = mMedia.getFilename();
if (TextUtils.isEmpty(filename)) {
return;
}
// We now check whether the file is found in the local filesystem, and update the path if it's not
File file = new File(filename);
if (!file.exists() && isReadOnly()) {
// Looks like the image is not present in the filesystem (i.e. remote URL)
// Update response, matching the local path. Note: In the future, media responses should
// not leak filesystem paths, for these are not guaranteed to be homogeneous in all devices.
file = new File(FileUtil.getFilesDir(FileUtil.FileType.MEDIA), file.getName());
mMedia.setFilename(file.getAbsolutePath());
captureResponse();
}
displayLocationInfo();
}
/**
* clears the file path and the complete icon
*/
@Override
public void resetQuestion(boolean fireEvent) {
super.resetQuestion(fireEvent);
mMedia = null;
mImageView.setImageDrawable(null);
hideDownloadOptions();
mLocationInfo.setVisibility(GONE);
mLocationListener.stop();
}
@Override
public void captureResponse(boolean suppressListeners) {
QuestionResponse response = null;
if (mMedia != null && !TextUtils.isEmpty(mMedia.getFilename())) {
response = new QuestionResponse(MediaValue.serialize(mMedia),
isImage() ? ConstantUtil.IMAGE_RESPONSE_TYPE : ConstantUtil.VIDEO_RESPONSE_TYPE,
getQuestion().getId());
response.setFilename(mMedia.getFilename());
}
setResponse(response);
}
@Override
public void onDestroy() {
if (mLocationListener.isListening()) {
mLocationListener.stop();
}
}
private void displayThumbnail() {
hideDownloadOptions();
String filename = mMedia != null ? mMedia.getFilename() : null;
if (TextUtils.isEmpty(filename)) {
return;
}
if (!new File(filename).exists()) {
mImageView.setImageResource(R.drawable.blurry_image);
mDownloadBtn.setVisibility(VISIBLE);
} else if (isImage()) {
// Image thumbnail
ImageUtil.displayImage(mImageView, filename);
} else {
// Video thumbnail
mImageView.setImageBitmap(ThumbnailUtils.createVideoThumbnail(
filename, MediaStore.Video.Thumbnails.MINI_KIND));
}
}
private boolean isImage() {
return ConstantUtil.PHOTO_QUESTION_TYPE.equals(mMediaType);
}
@Override
public void onResourceDownload(boolean done) {
if (!done) {
Toast.makeText(getContext(), R.string.error_img_preview, Toast.LENGTH_SHORT).show();
}
displayThumbnail();
displayLocationInfo();
}
@Override
public void onLocationReady(double latitude, double longitude, double altitude, float accuracy) {
if (accuracy > TimedLocationListener.ACCURACY_DEFAULT) {
// This location is not accurate enough. Keep listening for updates
return;
}
mLocationListener.stop();
if (mMedia != null) {
Location location = new Location();
location.setLatitude(latitude);
location.setLongitude(longitude);
location.setAltitude(altitude);
location.setAccuracy(accuracy);
mMedia.setLocation(location);
// Add location to EXIF too
ImageUtil.setLocation(mMedia.getFilename(), latitude, longitude);
captureResponse();
displayLocationInfo();
}
}
@Override
public void onTimeout() {
displayLocationInfo();
}
@Override
public void onGPSDisabled() {
displayLocationInfo();
}
private void displayLocationInfo() {
String filename = mMedia != null ? mMedia.getFilename() : null;
if (TextUtils.isEmpty(filename) || !new File(filename).exists()) {
mLocationInfo.setVisibility(GONE);
return;
}
mLocationInfo.setVisibility(VISIBLE);
float[] location = ImageUtil.getLocation(filename);
if (location != null) {
mLocationInfo.setText(R.string.image_location_saved);
} else if (mLocationListener.isListening()) {
mLocationInfo.setText(R.string.image_location_reading);
} else {
mLocationInfo.setText(R.string.image_location_unknown);
}
}
}