blob: 38b403b1068d5e3e508531a803c7ba44a128ec17 [file] [log] [blame]
/*
* Copyright (C) 2012 The Android Open Source Project
*
* 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 com.android.gallery3d.app;
import android.app.ActionBar;
import android.app.Activity;
import android.app.ProgressDialog;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.media.MediaPlayer;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.provider.MediaStore;
import android.provider.MediaStore.Video;
import android.provider.MediaStore.Video.VideoColumns;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.widget.TextView;
import android.widget.Toast;
import android.widget.VideoView;
import com.android.gallery3d.R;
import com.android.gallery3d.util.BucketNames;
import java.io.File;
import java.io.IOException;
import java.sql.Date;
import java.text.SimpleDateFormat;
public class TrimVideo extends Activity implements
MediaPlayer.OnErrorListener,
MediaPlayer.OnCompletionListener,
ControllerOverlay.Listener {
private VideoView mVideoView;
private TrimControllerOverlay mController;
private Context mContext;
private Uri mUri;
private final Handler mHandler = new Handler();
public static final String TRIM_ACTION = "com.android.camera.action.TRIM";
public ProgressDialog mProgress;
private int mTrimStartTime = 0;
private int mTrimEndTime = 0;
private int mVideoPosition = 0;
public static final String KEY_TRIM_START = "trim_start";
public static final String KEY_TRIM_END = "trim_end";
public static final String KEY_VIDEO_POSITION = "video_pos";
private boolean mHasPaused = false;
private String mSrcVideoPath = null;
private String mSaveFileName = null;
private static final String TIME_STAMP_NAME = "'TRIM'_yyyyMMdd_HHmmss";
private File mSrcFile = null;
private File mDstFile = null;
private File mSaveDirectory = null;
// For showing the result.
private String saveFolderName = null;
@Override
public void onCreate(Bundle savedInstanceState) {
mContext = getApplicationContext();
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_ACTION_BAR);
requestWindowFeature(Window.FEATURE_ACTION_BAR_OVERLAY);
ActionBar actionBar = getActionBar();
int displayOptions = ActionBar.DISPLAY_SHOW_HOME;
actionBar.setDisplayOptions(0, displayOptions);
displayOptions = ActionBar.DISPLAY_SHOW_CUSTOM;
actionBar.setDisplayOptions(displayOptions, displayOptions);
actionBar.setCustomView(R.layout.trim_menu);
TextView mSaveVideoTextView = (TextView) findViewById(R.id.start_trim);
mSaveVideoTextView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View arg0) {
trimVideo();
}
});
Intent intent = getIntent();
mUri = intent.getData();
mSrcVideoPath = intent.getStringExtra(PhotoPage.KEY_MEDIA_ITEM_PATH);
setContentView(R.layout.trim_view);
View rootView = findViewById(R.id.trim_view_root);
mVideoView = (VideoView) rootView.findViewById(R.id.surface_view);
mController = new TrimControllerOverlay(mContext);
((ViewGroup) rootView).addView(mController.getView());
mController.setListener(this);
mController.setCanReplay(true);
mVideoView.setOnErrorListener(this);
mVideoView.setOnCompletionListener(this);
mVideoView.setVideoURI(mUri);
playVideo();
}
@Override
public void onResume() {
super.onResume();
if (mHasPaused) {
mVideoView.seekTo(mVideoPosition);
mVideoView.resume();
mHasPaused = false;
}
mHandler.post(mProgressChecker);
}
@Override
public void onPause() {
mHasPaused = true;
mHandler.removeCallbacksAndMessages(null);
mVideoPosition = mVideoView.getCurrentPosition();
mVideoView.suspend();
super.onPause();
}
@Override
public void onStop() {
if (mProgress != null) {
mProgress.dismiss();
mProgress = null;
}
super.onStop();
}
@Override
public void onDestroy() {
mVideoView.stopPlayback();
super.onDestroy();
}
private final Runnable mProgressChecker = new Runnable() {
@Override
public void run() {
int pos = setProgress();
mHandler.postDelayed(mProgressChecker, 200 - (pos % 200));
}
};
@Override
public void onSaveInstanceState(Bundle savedInstanceState) {
savedInstanceState.putInt(KEY_TRIM_START, mTrimStartTime);
savedInstanceState.putInt(KEY_TRIM_END, mTrimEndTime);
savedInstanceState.putInt(KEY_VIDEO_POSITION, mVideoPosition);
super.onSaveInstanceState(savedInstanceState);
}
@Override
public void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
mTrimStartTime = savedInstanceState.getInt(KEY_TRIM_START, 0);
mTrimEndTime = savedInstanceState.getInt(KEY_TRIM_END, 0);
mVideoPosition = savedInstanceState.getInt(KEY_VIDEO_POSITION, 0);
}
// This updates the time bar display (if necessary). It is called by
// mProgressChecker and also from places where the time bar needs
// to be updated immediately.
private int setProgress() {
mVideoPosition = mVideoView.getCurrentPosition();
// If the video position is smaller than the starting point of trimming,
// correct it.
if (mVideoPosition < mTrimStartTime) {
mVideoView.seekTo(mTrimStartTime);
mVideoPosition = mTrimStartTime;
}
// If the position is bigger than the end point of trimming, show the
// replay button and pause.
if (mVideoPosition >= mTrimEndTime && mTrimEndTime > 0) {
if (mVideoPosition > mTrimEndTime) {
mVideoView.seekTo(mTrimEndTime);
mVideoPosition = mTrimEndTime;
}
mController.showEnded();
mVideoView.pause();
}
int duration = mVideoView.getDuration();
if (duration > 0 && mTrimEndTime == 0) {
mTrimEndTime = duration;
}
mController.setTimes(mVideoPosition, duration, mTrimStartTime, mTrimEndTime);
return mVideoPosition;
}
private void playVideo() {
mVideoView.start();
mController.showPlaying();
setProgress();
}
private void pauseVideo() {
mVideoView.pause();
mController.showPaused();
}
// Copy from SaveCopyTask.java in terms of how to handle the destination
// path and filename : querySource() and getSaveDirectory().
private interface ContentResolverQueryCallback {
void onCursorResult(Cursor cursor);
}
private void querySource(String[] projection, ContentResolverQueryCallback callback) {
ContentResolver contentResolver = getContentResolver();
Cursor cursor = null;
try {
cursor = contentResolver.query(mUri, projection, null, null, null);
if ((cursor != null) && cursor.moveToNext()) {
callback.onCursorResult(cursor);
}
} catch (Exception e) {
// Ignore error for lacking the data column from the source.
} finally {
if (cursor != null) {
cursor.close();
}
}
}
private File getSaveDirectory() {
final File[] dir = new File[1];
querySource(new String[] {
VideoColumns.DATA }, new ContentResolverQueryCallback() {
@Override
public void onCursorResult(Cursor cursor) {
dir[0] = new File(cursor.getString(0)).getParentFile();
}
});
return dir[0];
}
private void trimVideo() {
int delta = mTrimEndTime - mTrimStartTime;
// Considering that we only trim at sync frame, we don't want to trim
// when the time interval is too short or too close to the origin.
if (delta < 100 ) {
Toast.makeText(getApplicationContext(),
getString(R.string.trim_too_short),
Toast.LENGTH_SHORT).show();
return;
}
if (Math.abs(mVideoView.getDuration() - delta) < 100) {
// If no change has been made, go back
onBackPressed();
return;
}
// Use the default save directory if the source directory cannot be
// saved.
mSaveDirectory = getSaveDirectory();
if ((mSaveDirectory == null) || !mSaveDirectory.canWrite()) {
mSaveDirectory = new File(Environment.getExternalStorageDirectory(),
BucketNames.DOWNLOAD);
saveFolderName = getString(R.string.folder_download);
} else {
saveFolderName = mSaveDirectory.getName();
}
mSaveFileName = new SimpleDateFormat(TIME_STAMP_NAME).format(
new Date(System.currentTimeMillis()));
mDstFile = new File(mSaveDirectory, mSaveFileName + ".mp4");
mSrcFile = new File(mSrcVideoPath);
showProgressDialog();
new Thread(new Runnable() {
@Override
public void run() {
try {
TrimVideoUtils.startTrim(mSrcFile, mDstFile, mTrimStartTime, mTrimEndTime);
// Update the database for adding a new video file.
insertContent(mDstFile);
} catch (IOException e) {
e.printStackTrace();
}
// After trimming is done, trigger the UI changed.
mHandler.post(new Runnable() {
@Override
public void run() {
Toast.makeText(getApplicationContext(),
getString(R.string.save_into) + " " + saveFolderName,
Toast.LENGTH_SHORT)
.show();
// TODO: change trimming into a service to avoid
// this progressDialog and add notification properly.
if (mProgress != null) {
mProgress.dismiss();
mProgress = null;
// Show the result only when the activity not stopped.
Intent intent = new Intent(android.content.Intent.ACTION_VIEW);
intent.setDataAndTypeAndNormalize(Uri.fromFile(mDstFile), "video/*");
intent.putExtra(MediaStore.EXTRA_FINISH_ON_COMPLETION, false);
startActivity(intent);
finish();
}
}
});
}
}).start();
}
private void showProgressDialog() {
// create a background thread to trim the video.
// and show the progress.
mProgress = new ProgressDialog(this);
mProgress.setTitle(getString(R.string.trimming));
mProgress.setMessage(getString(R.string.please_wait));
// TODO: make this cancelable.
mProgress.setCancelable(false);
mProgress.setCanceledOnTouchOutside(false);
mProgress.show();
}
/**
* Insert the content (saved file) with proper video properties.
*/
private Uri insertContent(File file) {
long nowInMs = System.currentTimeMillis();
long nowInSec = nowInMs / 1000;
final ContentValues values = new ContentValues(12);
values.put(Video.Media.TITLE, mSaveFileName);
values.put(Video.Media.DISPLAY_NAME, file.getName());
values.put(Video.Media.MIME_TYPE, "video/mp4");
values.put(Video.Media.DATE_TAKEN, nowInMs);
values.put(Video.Media.DATE_MODIFIED, nowInSec);
values.put(Video.Media.DATE_ADDED, nowInSec);
values.put(Video.Media.DATA, file.getAbsolutePath());
values.put(Video.Media.SIZE, file.length());
// Copy the data taken and location info from src.
String[] projection = new String[] {
VideoColumns.DATE_TAKEN,
VideoColumns.LATITUDE,
VideoColumns.LONGITUDE,
VideoColumns.RESOLUTION,
};
// Copy some info from the source file.
querySource(projection, new ContentResolverQueryCallback() {
@Override
public void onCursorResult(Cursor cursor) {
long timeTaken = cursor.getLong(0);
if (timeTaken > 0) {
values.put(Video.Media.DATE_TAKEN, timeTaken);
}
double latitude = cursor.getDouble(1);
double longitude = cursor.getDouble(2);
// TODO: Change || to && after the default location issue is
// fixed.
if ((latitude != 0f) || (longitude != 0f)) {
values.put(Video.Media.LATITUDE, latitude);
values.put(Video.Media.LONGITUDE, longitude);
}
values.put(Video.Media.RESOLUTION, cursor.getString(3));
}
});
return getContentResolver().insert(Video.Media.EXTERNAL_CONTENT_URI, values);
}
@Override
public void onPlayPause() {
if (mVideoView.isPlaying()) {
pauseVideo();
} else {
playVideo();
}
}
@Override
public void onSeekStart() {
pauseVideo();
}
@Override
public void onSeekMove(int time) {
mVideoView.seekTo(time);
}
@Override
public void onSeekEnd(int time, int start, int end) {
mVideoView.seekTo(time);
mTrimStartTime = start;
mTrimEndTime = end;
setProgress();
}
@Override
public void onShown() {
}
@Override
public void onHidden() {
}
@Override
public void onReplay() {
mVideoView.seekTo(mTrimStartTime);
playVideo();
}
@Override
public void onCompletion(MediaPlayer mp) {
mController.showEnded();
}
@Override
public boolean onError(MediaPlayer mp, int what, int extra) {
return false;
}
}