package org.commcare.views.media;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.Point;
import android.media.MediaPlayer;
import android.net.Uri;
import android.os.Build;
import android.support.annotation.IdRes;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.Display;
import android.view.View;
import android.view.WindowManager;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.MediaController;
import android.widget.RelativeLayout;
import android.widget.TextView;
import android.widget.Toast;
import android.widget.VideoView;
import org.commcare.dalvik.R;
import org.commcare.preferences.CommCarePreferences;
import org.commcare.preferences.DeveloperPreferences;
import org.commcare.utils.FileUtil;
import org.commcare.utils.MediaUtil;
import org.commcare.utils.QRCodeEncoder;
import org.commcare.views.ResizingImageView;
import org.javarosa.core.reference.InvalidReferenceException;
import org.javarosa.core.reference.ReferenceManager;
import java.io.File;
/**
* This layout is used anywhere we can have image/audio/video/text.
* TODO: Put this in a layout file!!!!
*
* @author carlhartung
*/
public class MediaLayout extends RelativeLayout {
private static final String TAG = MediaLayout.class.getSimpleName();
@IdRes
public static final int INLINE_VIDEO_PANE_ID = 99999;
@IdRes
private static final int QUESTION_TEXT_PANE_ID = 2342134;
@IdRes
private static final int AUDIO_BUTTON_ID = 3245345;
@IdRes
private static final int VIDEO_BUTTON_ID = 234982340;
@IdRes
private static final int IMAGE_VIEW_ID = 23423534;
@IdRes
private static final int MISSING_IMAGE_ID = 234873453;
private TextView viewText;
private AudioPlaybackButton audioButton;
private ImageButton videoButton;
private TextView missingImageText;
private MediaLayout(Context c) {
super(c);
viewText = null;
audioButton = null;
missingImageText = null;
videoButton = null;
}
public static MediaLayout buildAudioImageLayout(Context context, TextView text, String audioURI, String imageURI) {
MediaLayout mediaLayout = new MediaLayout(context);
mediaLayout.setAVT(text, audioURI, imageURI, null, null, null, null, null, true, 0);
return mediaLayout;
}
public static MediaLayout buildAudioImageVisualLayout(Context context,
TextView text, String audioURI, String imageURI,
final String videoURI, final String bigImageURI) {
MediaLayout mediaLayout = new MediaLayout(context);
mediaLayout.setAVT(text, audioURI, imageURI, videoURI, bigImageURI, null, null, null, false, 0);
return mediaLayout;
}
public static MediaLayout buildComprehensiveLayout(Context context,
TextView text, String audioURI, String imageURI,
final String videoURI, final String bigImageURI,
final String qrCodeContent, String inlineVideoURI,
String expandedAudioUri, int questionIndex) {
MediaLayout mediaLayout = new MediaLayout(context);
mediaLayout.setAVT(text, audioURI, imageURI, videoURI, bigImageURI, qrCodeContent, inlineVideoURI, expandedAudioUri, false, questionIndex);
return mediaLayout;
}
private void setAVT(TextView text, String audioURI, String imageURI,
final String videoURI, final String bigImageURI,
final String qrCodeContent, String inlineVideoURI,
String expandedAudioURI, boolean showImageAboveText,
int questionIndex) {
viewText = text;
RelativeLayout.LayoutParams mediaPaneParams =
new RelativeLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
RelativeLayout questionTextPane = new RelativeLayout(this.getContext());
questionTextPane.setId(QUESTION_TEXT_PANE_ID);
setupStandardAudio(audioURI, questionIndex);
setupVideoButton(videoURI);
// Now set up the center view -- it is either an image, a QR Code, an inline video, or
// expanded audio
View mediaPane = null;
if (inlineVideoURI != null) {
mediaPane = getInlineVideoView(inlineVideoURI, mediaPaneParams);
} else if (qrCodeContent != null) {
mediaPane = setupQRView(qrCodeContent);
} else if (imageURI != null) {
mediaPane = setupImage(imageURI, bigImageURI);
} else if (expandedAudioURI != null) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
mediaPane = setupExpandedAudio(expandedAudioURI, questionIndex);
} else {
setupStandardAudio(expandedAudioURI, questionIndex);
}
}
addAudioVideoButtonsToView(questionTextPane);
showImageAboveText = showImageAboveText ||
DeveloperPreferences.imageAboveTextEnabled();
addElementsToView(mediaPane, mediaPaneParams, questionTextPane, showImageAboveText);
}
private void setupVideoButton(final String videoURI) {
if (videoURI != null) {
videoButton = new ImageButton(getContext());
videoButton.setImageResource(android.R.drawable.ic_media_play);
videoButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
String videoFilename = "";
try {
videoFilename =
ReferenceManager.instance().DeriveReference(videoURI).getLocalURI();
} catch (InvalidReferenceException e) {
Log.e(TAG, "Invalid reference exception");
e.printStackTrace();
}
File videoFile = new File(videoFilename);
if (!videoFile.exists()) {
// We should have a video clip, but the file doesn't exist.
String errorMsg =
getContext().getString(R.string.file_missing, videoFilename);
Log.e(TAG, errorMsg);
Toast.makeText(getContext(), errorMsg, Toast.LENGTH_LONG).show();
return;
}
Intent i = new Intent("android.intent.action.VIEW");
Uri videoFileUri = FileUtil.getUriForExternalFile(getContext(), videoFile);
i.setDataAndType(videoFileUri, "video/*");
i.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
try {
getContext().startActivity(i);
} catch (ActivityNotFoundException e) {
Toast.makeText(getContext(),
getContext().getString(R.string.activity_not_found, "view video"),
Toast.LENGTH_SHORT).show();
}
}
});
videoButton.setId(VIDEO_BUTTON_ID);
}
}
private void addAudioVideoButtonsToView(RelativeLayout questionTextPane) {
RelativeLayout.LayoutParams textParams =
new RelativeLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
RelativeLayout.LayoutParams audioParams =
new RelativeLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
RelativeLayout.LayoutParams videoParams =
new RelativeLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
// Add the audioButton and videoButton (if applicable) and view
// (containing text) to the relative layout.
if (audioButton != null) {
if (videoButton == null) {
audioParams.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);
textParams.addRule(RelativeLayout.LEFT_OF, audioButton.getId());
questionTextPane.addView(audioButton, audioParams);
} else {
audioParams.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);
textParams.addRule(RelativeLayout.LEFT_OF, audioButton.getId());
videoParams.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);
videoParams.addRule(RelativeLayout.BELOW, audioButton.getId());
questionTextPane.addView(audioButton, audioParams);
questionTextPane.addView(videoButton, videoParams);
}
} else if (videoButton != null) {
videoParams.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);
textParams.addRule(RelativeLayout.LEFT_OF, videoButton.getId());
questionTextPane.addView(videoButton, videoParams);
} else {
//Audio and Video are both null, let text bleed to right
textParams.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);
}
if (viewText.getVisibility() != GONE) {
textParams.addRule(RelativeLayout.ALIGN_PARENT_LEFT);
questionTextPane.addView(viewText, textParams);
}
}
private void setupStandardAudio(String audioURI, int questionIndex) {
if (audioURI != null) {
audioButton = new AudioPlaybackButton(getContext(), audioURI,
ViewId.buildListViewId(questionIndex), true);
// random ID to be used by the relative layout.
audioButton.setId(AUDIO_BUTTON_ID);
}
}
private View setupExpandedAudio(String expandedAudioURI, int questionIndex) {
return new ExpandedAudioPlaybackView(getContext(), expandedAudioURI, questionIndex);
}
private View setupQRView(String qrCodeContent) {
Bitmap image;
int minimumDim = getScreenMinimumDimension();
try {
QRCodeEncoder qrCodeEncoder =
new QRCodeEncoder(qrCodeContent, minimumDim);
image = qrCodeEncoder.encodeAsBitmap();
ImageView imageView = new ImageView(getContext());
imageView.setPadding(10, 10, 10, 10);
imageView.setAdjustViewBounds(true);
imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
imageView.setImageBitmap(image);
imageView.setId(IMAGE_VIEW_ID);
return imageView;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
private View setupImage(String imageURI, String bigImageURI) {
String errorMsg = null;
View mediaPane = null;
try {
int[] maxBounds = getMaxCenterViewBounds();
final String imageFilename = ReferenceManager.instance().DeriveReference(imageURI).getLocalURI();
final File imageFile = new File(imageFilename);
if (imageFile.exists()) {
Bitmap b = MediaUtil.inflateDisplayImage(getContext(), imageURI, maxBounds[0],
maxBounds[1]);
if (b != null) {
ImageView mImageView = new ImageView(getContext());
if (useResizingImageView()) {
mImageView = new ResizingImageView(getContext(), imageURI, bigImageURI);
mImageView.setAdjustViewBounds(true);
mImageView.setMaxWidth(maxBounds[0]);
mImageView.setMaxHeight(maxBounds[1]);
} else {
mImageView.setScaleType(ImageView.ScaleType.CENTER);
}
mImageView.setPadding(10, 10, 10, 10);
mImageView.setImageBitmap(b);
mImageView.setId(IMAGE_VIEW_ID);
mediaPane = mImageView;
}
} else {
// An error hasn't been logged. We should have an image, but the file doesn't
// exist.
errorMsg = getContext().getString(R.string.file_missing, imageFile);
}
if (errorMsg != null) {
// errorMsg is only set when an error has occured
Log.e(TAG, errorMsg);
missingImageText = new TextView(getContext());
missingImageText.setText(errorMsg);
missingImageText.setPadding(10, 10, 10, 10);
missingImageText.setId(MISSING_IMAGE_ID);
mediaPane = missingImageText;
}
} catch (InvalidReferenceException e) {
Log.e(TAG, "image invalid reference exception");
e.printStackTrace();
}
return mediaPane;
}
private void addElementsToView(View mediaPane,
RelativeLayout.LayoutParams mediaPaneParams,
RelativeLayout questionTextPane, boolean showImageAboveText) {
RelativeLayout.LayoutParams questionTextPaneParams =
new RelativeLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
if (mediaPane != null) {
if (viewText.getVisibility() == GONE) {
this.addView(questionTextPane, questionTextPaneParams);
if (audioButton != null) {
mediaPaneParams.addRule(RelativeLayout.LEFT_OF, audioButton.getId());
questionTextPane.addView(mediaPane, mediaPaneParams);
}
if (videoButton != null) {
mediaPaneParams.addRule(RelativeLayout.LEFT_OF, videoButton.getId());
questionTextPane.addView(mediaPane, mediaPaneParams);
}
} else {
if (showImageAboveText) {
mediaPaneParams.addRule(CENTER_HORIZONTAL);
this.addView(mediaPane, mediaPaneParams);
questionTextPaneParams.addRule(RelativeLayout.BELOW, mediaPane.getId());
this.addView(questionTextPane, questionTextPaneParams);
} else {
this.addView(questionTextPane, questionTextPaneParams);
mediaPaneParams.addRule(RelativeLayout.BELOW, questionTextPane.getId());
mediaPaneParams.addRule(CENTER_HORIZONTAL);
this.addView(mediaPane, mediaPaneParams);
}
}
} else {
this.addView(questionTextPane, questionTextPaneParams);
}
}
@SuppressWarnings("deprecation")
private int getScreenMinimumDimension() {
Display display =
((WindowManager)getContext().getSystemService(Context.WINDOW_SERVICE))
.getDefaultDisplay();
int width, height;
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB_MR2) {
width = display.getWidth();
height = display.getHeight();
} else {
Point screenDims = new Point();
display.getSize(screenDims);
width = screenDims.x;
height = screenDims.y;
}
return Math.min(width, height);
}
/**
* Creates a video view for the provided URI or an error view elaborating why the video
* couldn't be displayed.
*
* @param inlineVideoURI JavaRosa Reference URI
* @param viewLayoutParams the layout params that will be applied to the view. Expect to be
* mutated by this method
*/
private View getInlineVideoView(String inlineVideoURI, RelativeLayout.LayoutParams viewLayoutParams) {
try {
final String videoFilename = ReferenceManager.instance().DeriveReference(inlineVideoURI).getLocalURI();
int[] maxBounds = getMaxCenterViewBounds();
File videoFile = new File(videoFilename);
if (!videoFile.exists()) {
return getMissingImageView("No video file found at: " + videoFilename);
} else {
//NOTE: This has odd behavior when you have a text input on the screen
//since clicking the video view to bring up controls has weird effects.
//since we shotgun grab the focus for the input widget.
final MediaController ctrl = new MediaController(this.getContext());
VideoView videoView = new VideoView(this.getContext());
videoView.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
@Override
public void onPrepared(MediaPlayer mediaPlayer) {
ctrl.show();
}
});
videoView.setVideoPath(videoFilename);
videoView.setMediaController(ctrl);
ctrl.setAnchorView(videoView);
//These surprisingly get re-jiggered as soon as the video is loaded, so we
//just want to give it the _max_ bounds, it'll pick the limiter and shrink
//itself when it's ready.
viewLayoutParams.width = maxBounds[0];
viewLayoutParams.height = maxBounds[1];
videoView.setId(INLINE_VIDEO_PANE_ID);
return videoView;
}
} catch (InvalidReferenceException ire) {
Log.e(TAG, "invalid video reference exception");
ire.printStackTrace();
return getMissingImageView("Invalid reference: " + ire.getReferenceString());
}
}
private TextView getMissingImageView(String errorMessage) {
missingImageText = new TextView(getContext());
missingImageText.setText(errorMessage);
missingImageText.setPadding(10, 10, 10, 10);
missingImageText.setId(MISSING_IMAGE_ID);
return missingImageText;
}
private boolean useResizingImageView() {
// only allow ResizingImageView to be used if not also using smart inflation
return !CommCarePreferences.isSmartInflationEnabled() &&
("full".equals(ResizingImageView.resizeMethod)
|| "half".equals(ResizingImageView.resizeMethod)
|| "width".equals(ResizingImageView.resizeMethod));
}
/**
* @return The appropriate max size of an image view pane in this widget. returned as an int
* array of [width, height]
*/
private int[] getMaxCenterViewBounds() {
DisplayMetrics metrics = this.getContext().getResources().getDisplayMetrics();
int maxWidth = metrics.widthPixels;
int maxHeight = metrics.heightPixels;
// subtract height for textview and buttons, if present
if (viewText != null) {
maxHeight = maxHeight - viewText.getHeight();
}
if (videoButton != null) {
maxHeight = maxHeight - videoButton.getHeight();
} else if (audioButton != null) {
maxHeight = maxHeight - audioButton.getHeight();
}
// reduce by third for safety
return new int[]{maxWidth, (2 * maxHeight) / 3};
}
/**
* This adds a divider at the bottom of this layout. Used to separate
* fields in lists.
*/
public void addDivider(ImageView v) {
RelativeLayout.LayoutParams dividerParams =
new RelativeLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
if (missingImageText != null) {
dividerParams.addRule(RelativeLayout.BELOW, missingImageText.getId());
} else if (videoButton != null) {
dividerParams.addRule(RelativeLayout.BELOW, videoButton.getId());
} else if (audioButton != null) {
dividerParams.addRule(RelativeLayout.BELOW, audioButton.getId());
} else if (viewText != null) {
// No picture
dividerParams.addRule(RelativeLayout.BELOW, viewText.getId());
} else {
Log.e(TAG, "Tried to add divider to uninitialized ATVWidget");
return;
}
addView(v, dividerParams);
}
}