Java tutorial
/* * Copyright (C) 2009 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.annotation.SuppressLint; import android.app.Activity; import android.content.ActivityNotFoundException; import android.content.ContentValues; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.net.Uri; import android.os.Build; import android.os.Environment; import android.preference.PreferenceManager; import android.provider.MediaStore.Video; import android.support.annotation.NonNull; import android.support.v4.content.FileProvider; import android.view.View; import android.widget.Button; import android.widget.LinearLayout; 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.BuildConfig; import org.odk.collect.android.R; import org.odk.collect.android.activities.CaptureSelfieVideoActivity; import org.odk.collect.android.activities.CaptureSelfieVideoActivityNewApi; import org.odk.collect.android.application.Collect; import org.odk.collect.android.listeners.PermissionListener; import org.odk.collect.android.preferences.GeneralKeys; import org.odk.collect.android.utilities.CameraUtils; import org.odk.collect.android.utilities.FileUtil; import org.odk.collect.android.utilities.FileUtils; import org.odk.collect.android.utilities.MediaManager; import org.odk.collect.android.utilities.MediaUtil; import org.odk.collect.android.utilities.ToastUtils; import org.odk.collect.android.widgets.interfaces.FileWidget; import java.io.File; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Locale; import timber.log.Timber; import static android.os.Build.MODEL; import static org.odk.collect.android.utilities.ApplicationConstants.RequestCodes; /** * Widget that allows user to take pictures, sounds or video and add them to the * form. * * @author Carl Hartung (carlhartung@gmail.com) * @author Yaw Anokwa (yanokwa@gmail.com) */ @SuppressLint("ViewConstructor") public class VideoWidget extends QuestionWidget implements FileWidget { public static final boolean DEFAULT_HIGH_RESOLUTION = true; public static final int MEDIA_TYPE_IMAGE = 1; public static final int MEDIA_TYPE_VIDEO = 2; public static final String NEXUS7 = "Nexus 7"; private static final String DIRECTORY_PICTURES = "Pictures"; @NonNull private MediaUtil mediaUtil; @NonNull private FileUtil fileUtil; private Button captureButton; private Button playButton; private Button chooseButton; private String binaryName; private Uri nexus7Uri; private boolean selfie; public VideoWidget(Context context, FormEntryPrompt prompt) { this(context, prompt, new FileUtil(), new MediaUtil()); } public VideoWidget(Context context, FormEntryPrompt prompt, @NonNull FileUtil fileUtil, @NonNull MediaUtil mediaUtil) { super(context, prompt); this.fileUtil = fileUtil; this.mediaUtil = mediaUtil; String appearance = getFormEntryPrompt().getAppearanceHint(); selfie = appearance != null && (appearance.equalsIgnoreCase("selfie") || appearance.equalsIgnoreCase("new-front")); captureButton = getSimpleButton(getContext().getString(R.string.capture_video), R.id.capture_video); chooseButton = getSimpleButton(getContext().getString(R.string.choose_video), R.id.choose_video); playButton = getSimpleButton(getContext().getString(R.string.play_video), R.id.play_video); // retrieve answer from data model and update ui binaryName = prompt.getAnswerText(); if (binaryName != null) { playButton.setEnabled(true); } else { playButton.setEnabled(false); } // finish complex layout LinearLayout answerLayout = new LinearLayout(getContext()); answerLayout.setOrientation(LinearLayout.VERTICAL); answerLayout.addView(captureButton); answerLayout.addView(chooseButton); answerLayout.addView(playButton); addAnswerView(answerLayout); hideButtonsIfNeeded(); if (selfie) { if (!CameraUtils.isFrontCameraAvailable()) { captureButton.setEnabled(false); ToastUtils.showLongToast(R.string.error_front_camera_unavailable); } } } /* * Create a file Uri for saving an image or video * For Nexus 7 fix ... * See http://developer.android.com/guide/topics/media/camera.html for more info */ private static Uri getOutputMediaFileUri(int type) { return Uri.fromFile(getOutputMediaFile(type)); } /* * Create a File for saving an image or video * For Nexus 7 fix ... * See http://developer.android.com/guide/topics/media/camera.html for more info */ public static File getOutputMediaFile(int type) { // To be safe, you should check that the SDCard is mounted // using Environment.getExternalStorageState() before doing this. File mediaStorageDir = new File(Environment.getExternalStorageDirectory(), DIRECTORY_PICTURES); // This location works best if you want the created images to be shared // between applications and persist after your app has been uninstalled. // Create the storage directory if it does not exist if (!mediaStorageDir.exists()) { if (!mediaStorageDir.mkdirs()) { Timber.d("failed to create directory"); return null; } } // Create a media file name String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmssSSSZ", Locale.US).format(new Date()); File mediaFile; if (type == MEDIA_TYPE_IMAGE) { mediaFile = new File(mediaStorageDir.getPath() + File.separator + "IMG_" + timeStamp + ".jpg"); } else if (type == MEDIA_TYPE_VIDEO) { mediaFile = new File(mediaStorageDir.getPath() + File.separator + "VID_" + timeStamp + ".mp4"); } else { return null; } return mediaFile; } @Override public void deleteFile() { MediaManager.INSTANCE.markOriginalFileOrDelete(getFormEntryPrompt().getIndex().toString(), getInstanceFolder() + File.separator + binaryName); binaryName = null; } @Override public void clearAnswer() { // remove the file deleteFile(); // reset buttons playButton.setEnabled(false); } @Override public IAnswerData getAnswer() { if (binaryName != null) { return new StringData(binaryName); } else { return null; } } /** * Set this widget with the actual file returned by OnActivityResult. * Both of Uri and File are supported. * If the file is local, a Uri is enough for the copy task below. * If the chose file is from cloud(such as Google Drive), * The retrieve and copy task is already executed in the previous step, * so a File object would be presented. * * @param object Uri or File of the chosen file. * @see org.odk.collect.android.activities.FormEntryActivity#onActivityResult(int, int, Intent) */ @Override public void setBinaryData(Object object) { File newVideo = null; // get the file path and create a copy in the instance folder if (object instanceof Uri) { String sourcePath = getSourcePathFromUri((Uri) object); String destinationPath = getDestinationPathFromSourcePath(sourcePath); File source = fileUtil.getFileAtPath(sourcePath); newVideo = fileUtil.getFileAtPath(destinationPath); fileUtil.copyFile(source, newVideo); } else if (object instanceof File) { newVideo = (File) object; } else { Timber.w("VideoWidget's setBinaryData must receive a File or Uri object."); return; } if (newVideo.exists()) { // Add the copy to the content provier ContentValues values = new ContentValues(6); values.put(Video.Media.TITLE, newVideo.getName()); values.put(Video.Media.DISPLAY_NAME, newVideo.getName()); values.put(Video.Media.DATE_ADDED, System.currentTimeMillis()); values.put(Video.Media.DATA, newVideo.getAbsolutePath()); MediaManager.INSTANCE.replaceRecentFileForQuestion(getFormEntryPrompt().getIndex().toString(), newVideo.getAbsolutePath()); Uri videoURI = getContext().getContentResolver().insert(Video.Media.EXTERNAL_CONTENT_URI, values); if (videoURI != null) { Timber.i("Inserting VIDEO returned uri = %s", videoURI.toString()); } } else { Timber.e("Inserting Video file FAILED"); } // you are replacing an answer. remove the media. if (binaryName != null && !binaryName.equals(newVideo.getName())) { deleteFile(); } binaryName = newVideo.getName(); // Need to have this ugly code to account for // a bug in the Nexus 7 on 4.3 not returning the mediaUri in the data // of the intent - uri in this case is a file if (NEXUS7.equals(MODEL) && Build.VERSION.SDK_INT == 18) { if (object instanceof File) { File fileToDelete = (File) object; int delCount = fileToDelete.delete() ? 1 : 0; Timber.i("Deleting original capture of file: %s count: %d", binaryName, delCount); } } } private void hideButtonsIfNeeded() { if (selfie || (getFormEntryPrompt().getAppearanceHint() != null && getFormEntryPrompt().getAppearanceHint().toLowerCase(Locale.ENGLISH).contains("new"))) { chooseButton.setVisibility(View.GONE); } } private String getSourcePathFromUri(@NonNull Uri uri) { return mediaUtil.getPathFromUri(getContext(), uri, Video.Media.DATA); } private String getDestinationPathFromSourcePath(@NonNull String sourcePath) { String extension = sourcePath.substring(sourcePath.lastIndexOf('.')); return getInstanceFolder() + File.separator + fileUtil.getRandomFilename() + extension; } @Override public void setOnLongClickListener(OnLongClickListener l) { captureButton.setOnLongClickListener(l); chooseButton.setOnLongClickListener(l); playButton.setOnLongClickListener(l); } @Override public void cancelLongPress() { super.cancelLongPress(); captureButton.cancelLongPress(); chooseButton.cancelLongPress(); playButton.cancelLongPress(); } @Override public void onButtonClick(int id) { switch (id) { case R.id.capture_video: if (selfie) { getPermissionUtils().requestCameraAndRecordAudioPermissions((Activity) getContext(), new PermissionListener() { @Override public void granted() { captureVideo(); } @Override public void denied() { } }); } else { getPermissionUtils().requestCameraPermission((Activity) getContext(), new PermissionListener() { @Override public void granted() { captureVideo(); } @Override public void denied() { } }); } break; case R.id.choose_video: chooseVideo(); break; case R.id.play_video: playVideoFile(); break; } } private void captureVideo() { Intent i; if (selfie) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { i = new Intent(getContext(), CaptureSelfieVideoActivityNewApi.class); } else { i = new Intent(getContext(), CaptureSelfieVideoActivity.class); } } else { i = new Intent(android.provider.MediaStore.ACTION_VIDEO_CAPTURE); // Need to have this ugly code to account for // a bug in the Nexus 7 on 4.3 not returning the mediaUri in the data // of the intent - using the MediaStore.EXTRA_OUTPUT to get the data // Have it saving to an intermediate location instead of final destination // to allow the current location to catch issues with the intermediate file Timber.i("The build of this device is %s", MODEL); if (NEXUS7.equals(MODEL) && Build.VERSION.SDK_INT == 18) { nexus7Uri = getOutputMediaFileUri(MEDIA_TYPE_VIDEO); i.putExtra(android.provider.MediaStore.EXTRA_OUTPUT, nexus7Uri); } else { i.putExtra(android.provider.MediaStore.EXTRA_OUTPUT, Video.Media.EXTERNAL_CONTENT_URI.toString()); } } SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(Collect.getInstance()); // request high resolution if configured for that... boolean highResolution = settings.getBoolean(GeneralKeys.KEY_HIGH_RESOLUTION, VideoWidget.DEFAULT_HIGH_RESOLUTION); if (highResolution) { i.putExtra(android.provider.MediaStore.EXTRA_VIDEO_QUALITY, 1); } try { waitForData(); ((Activity) getContext()).startActivityForResult(i, RequestCodes.VIDEO_CAPTURE); } catch (ActivityNotFoundException e) { Toast.makeText(getContext(), getContext().getString(R.string.activity_not_found, getContext().getString(R.string.capture_video)), Toast.LENGTH_SHORT).show(); cancelWaitingForData(); } } private void chooseVideo() { Intent i = new Intent(Intent.ACTION_GET_CONTENT); i.setType("video/*"); // Intent i = // new Intent(Intent.ACTION_PICK, // android.provider.MediaStore.Video.Media.EXTERNAL_CONTENT_URI); try { waitForData(); ((Activity) getContext()).startActivityForResult(i, RequestCodes.VIDEO_CHOOSER); } catch (ActivityNotFoundException e) { Toast.makeText(getContext(), getContext().getString(R.string.activity_not_found, getContext().getString(R.string.choose_video)), Toast.LENGTH_SHORT).show(); cancelWaitingForData(); } } private void playVideoFile() { Intent intent = new Intent("android.intent.action.VIEW"); File file = new File(getInstanceFolder() + File.separator + binaryName); Uri uri = FileProvider.getUriForFile(getContext(), BuildConfig.APPLICATION_ID + ".provider", file); FileUtils.grantFileReadPermissions(intent, uri, getContext()); intent.setDataAndType(uri, "video/*"); try { getContext().startActivity(intent); } catch (ActivityNotFoundException e) { Toast.makeText(getContext(), getContext().getString(R.string.activity_not_found, getContext().getString(R.string.view_video)), Toast.LENGTH_SHORT).show(); } } }