Home | History | Annotate | Download | only in app
      1 /*
      2  * Copyright (C) 2012 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.gallery3d.app;
     18 
     19 import android.app.ActionBar;
     20 import android.app.Activity;
     21 import android.app.ProgressDialog;
     22 import android.content.ContentResolver;
     23 import android.content.ContentValues;
     24 import android.content.Context;
     25 import android.content.Intent;
     26 import android.database.Cursor;
     27 import android.media.MediaPlayer;
     28 import android.net.Uri;
     29 import android.os.Bundle;
     30 import android.os.Environment;
     31 import android.os.Handler;
     32 import android.provider.MediaStore;
     33 import android.provider.MediaStore.Video;
     34 import android.provider.MediaStore.Video.VideoColumns;
     35 import android.view.View;
     36 import android.view.ViewGroup;
     37 import android.view.Window;
     38 import android.widget.TextView;
     39 import android.widget.Toast;
     40 import android.widget.VideoView;
     41 
     42 import com.android.gallery3d.R;
     43 import com.android.gallery3d.util.BucketNames;
     44 
     45 import java.io.File;
     46 import java.io.IOException;
     47 import java.sql.Date;
     48 import java.text.SimpleDateFormat;
     49 
     50 public class TrimVideo extends Activity implements
     51         MediaPlayer.OnErrorListener,
     52         MediaPlayer.OnCompletionListener,
     53         ControllerOverlay.Listener {
     54 
     55     private VideoView mVideoView;
     56     private TrimControllerOverlay mController;
     57     private Context mContext;
     58     private Uri mUri;
     59     private final Handler mHandler = new Handler();
     60     public static final String TRIM_ACTION = "com.android.camera.action.TRIM";
     61 
     62     public ProgressDialog mProgress;
     63 
     64     private int mTrimStartTime = 0;
     65     private int mTrimEndTime = 0;
     66     private int mVideoPosition = 0;
     67     public static final String KEY_TRIM_START = "trim_start";
     68     public static final String KEY_TRIM_END = "trim_end";
     69     public static final String KEY_VIDEO_POSITION = "video_pos";
     70     private boolean mHasPaused = false;
     71 
     72     private String mSrcVideoPath = null;
     73     private String mSaveFileName = null;
     74     private static final String TIME_STAMP_NAME = "'TRIM'_yyyyMMdd_HHmmss";
     75     private File mSrcFile = null;
     76     private File mDstFile = null;
     77     private File mSaveDirectory = null;
     78     // For showing the result.
     79     private String saveFolderName = null;
     80 
     81     @Override
     82     public void onCreate(Bundle savedInstanceState) {
     83         mContext = getApplicationContext();
     84         super.onCreate(savedInstanceState);
     85 
     86         requestWindowFeature(Window.FEATURE_ACTION_BAR);
     87         requestWindowFeature(Window.FEATURE_ACTION_BAR_OVERLAY);
     88 
     89         ActionBar actionBar = getActionBar();
     90         int displayOptions = ActionBar.DISPLAY_SHOW_HOME;
     91         actionBar.setDisplayOptions(0, displayOptions);
     92         displayOptions = ActionBar.DISPLAY_SHOW_CUSTOM;
     93         actionBar.setDisplayOptions(displayOptions, displayOptions);
     94         actionBar.setCustomView(R.layout.trim_menu);
     95 
     96         TextView mSaveVideoTextView = (TextView) findViewById(R.id.start_trim);
     97         mSaveVideoTextView.setOnClickListener(new View.OnClickListener() {
     98             @Override
     99             public void onClick(View arg0) {
    100                 trimVideo();
    101             }
    102         });
    103 
    104         Intent intent = getIntent();
    105         mUri = intent.getData();
    106         mSrcVideoPath = intent.getStringExtra(PhotoPage.KEY_MEDIA_ITEM_PATH);
    107         setContentView(R.layout.trim_view);
    108         View rootView = findViewById(R.id.trim_view_root);
    109 
    110         mVideoView = (VideoView) rootView.findViewById(R.id.surface_view);
    111 
    112         mController = new TrimControllerOverlay(mContext);
    113         ((ViewGroup) rootView).addView(mController.getView());
    114         mController.setListener(this);
    115         mController.setCanReplay(true);
    116 
    117         mVideoView.setOnErrorListener(this);
    118         mVideoView.setOnCompletionListener(this);
    119         mVideoView.setVideoURI(mUri);
    120 
    121         playVideo();
    122     }
    123 
    124     @Override
    125     public void onResume() {
    126         super.onResume();
    127         if (mHasPaused) {
    128             mVideoView.seekTo(mVideoPosition);
    129             mVideoView.resume();
    130             mHasPaused = false;
    131         }
    132         mHandler.post(mProgressChecker);
    133     }
    134 
    135     @Override
    136     public void onPause() {
    137         mHasPaused = true;
    138         mHandler.removeCallbacksAndMessages(null);
    139         mVideoPosition = mVideoView.getCurrentPosition();
    140         mVideoView.suspend();
    141         super.onPause();
    142     }
    143 
    144     @Override
    145     public void onStop() {
    146         if (mProgress != null) {
    147             mProgress.dismiss();
    148             mProgress = null;
    149         }
    150         super.onStop();
    151     }
    152 
    153     @Override
    154     public void onDestroy() {
    155         mVideoView.stopPlayback();
    156         super.onDestroy();
    157     }
    158 
    159     private final Runnable mProgressChecker = new Runnable() {
    160         @Override
    161         public void run() {
    162             int pos = setProgress();
    163             mHandler.postDelayed(mProgressChecker, 200 - (pos % 200));
    164         }
    165     };
    166 
    167     @Override
    168     public void onSaveInstanceState(Bundle savedInstanceState) {
    169         savedInstanceState.putInt(KEY_TRIM_START, mTrimStartTime);
    170         savedInstanceState.putInt(KEY_TRIM_END, mTrimEndTime);
    171         savedInstanceState.putInt(KEY_VIDEO_POSITION, mVideoPosition);
    172         super.onSaveInstanceState(savedInstanceState);
    173     }
    174 
    175     @Override
    176     public void onRestoreInstanceState(Bundle savedInstanceState) {
    177         super.onRestoreInstanceState(savedInstanceState);
    178         mTrimStartTime = savedInstanceState.getInt(KEY_TRIM_START, 0);
    179         mTrimEndTime = savedInstanceState.getInt(KEY_TRIM_END, 0);
    180         mVideoPosition = savedInstanceState.getInt(KEY_VIDEO_POSITION, 0);
    181     }
    182 
    183     // This updates the time bar display (if necessary). It is called by
    184     // mProgressChecker and also from places where the time bar needs
    185     // to be updated immediately.
    186     private int setProgress() {
    187         mVideoPosition = mVideoView.getCurrentPosition();
    188         // If the video position is smaller than the starting point of trimming,
    189         // correct it.
    190         if (mVideoPosition < mTrimStartTime) {
    191             mVideoView.seekTo(mTrimStartTime);
    192             mVideoPosition = mTrimStartTime;
    193         }
    194         // If the position is bigger than the end point of trimming, show the
    195         // replay button and pause.
    196         if (mVideoPosition >= mTrimEndTime && mTrimEndTime > 0) {
    197             if (mVideoPosition > mTrimEndTime) {
    198                 mVideoView.seekTo(mTrimEndTime);
    199                 mVideoPosition = mTrimEndTime;
    200             }
    201             mController.showEnded();
    202             mVideoView.pause();
    203         }
    204 
    205         int duration = mVideoView.getDuration();
    206         if (duration > 0 && mTrimEndTime == 0) {
    207             mTrimEndTime = duration;
    208         }
    209         mController.setTimes(mVideoPosition, duration, mTrimStartTime, mTrimEndTime);
    210         return mVideoPosition;
    211     }
    212 
    213     private void playVideo() {
    214         mVideoView.start();
    215         mController.showPlaying();
    216         setProgress();
    217     }
    218 
    219     private void pauseVideo() {
    220         mVideoView.pause();
    221         mController.showPaused();
    222     }
    223 
    224     // Copy from SaveCopyTask.java in terms of how to handle the destination
    225     // path and filename : querySource() and getSaveDirectory().
    226     private interface ContentResolverQueryCallback {
    227         void onCursorResult(Cursor cursor);
    228     }
    229 
    230     private void querySource(String[] projection, ContentResolverQueryCallback callback) {
    231         ContentResolver contentResolver = getContentResolver();
    232         Cursor cursor = null;
    233         try {
    234             cursor = contentResolver.query(mUri, projection, null, null, null);
    235             if ((cursor != null) && cursor.moveToNext()) {
    236                 callback.onCursorResult(cursor);
    237             }
    238         } catch (Exception e) {
    239             // Ignore error for lacking the data column from the source.
    240         } finally {
    241             if (cursor != null) {
    242                 cursor.close();
    243             }
    244         }
    245     }
    246 
    247     private File getSaveDirectory() {
    248         final File[] dir = new File[1];
    249         querySource(new String[] {
    250                 VideoColumns.DATA }, new ContentResolverQueryCallback() {
    251 
    252             @Override
    253             public void onCursorResult(Cursor cursor) {
    254                 dir[0] = new File(cursor.getString(0)).getParentFile();
    255             }
    256         });
    257         return dir[0];
    258     }
    259 
    260     private void trimVideo() {
    261         int delta = mTrimEndTime - mTrimStartTime;
    262         // Considering that we only trim at sync frame, we don't want to trim
    263         // when the time interval is too short or too close to the origin.
    264         if (delta < 100 ) {
    265             Toast.makeText(getApplicationContext(),
    266                 getString(R.string.trim_too_short),
    267                 Toast.LENGTH_SHORT).show();
    268             return;
    269         }
    270         if (Math.abs(mVideoView.getDuration() - delta) < 100) {
    271             // If no change has been made, go back
    272             onBackPressed();
    273             return;
    274         }
    275         // Use the default save directory if the source directory cannot be
    276         // saved.
    277         mSaveDirectory = getSaveDirectory();
    278         if ((mSaveDirectory == null) || !mSaveDirectory.canWrite()) {
    279             mSaveDirectory = new File(Environment.getExternalStorageDirectory(),
    280                     BucketNames.DOWNLOAD);
    281             saveFolderName = getString(R.string.folder_download);
    282         } else {
    283             saveFolderName = mSaveDirectory.getName();
    284         }
    285         mSaveFileName = new SimpleDateFormat(TIME_STAMP_NAME).format(
    286                 new Date(System.currentTimeMillis()));
    287 
    288         mDstFile = new File(mSaveDirectory, mSaveFileName + ".mp4");
    289         mSrcFile = new File(mSrcVideoPath);
    290 
    291         showProgressDialog();
    292 
    293         new Thread(new Runnable() {
    294             @Override
    295             public void run() {
    296                 try {
    297                     TrimVideoUtils.startTrim(mSrcFile, mDstFile, mTrimStartTime, mTrimEndTime);
    298                     // Update the database for adding a new video file.
    299                     insertContent(mDstFile);
    300                 } catch (IOException e) {
    301                     e.printStackTrace();
    302                 }
    303                 // After trimming is done, trigger the UI changed.
    304                 mHandler.post(new Runnable() {
    305                     @Override
    306                     public void run() {
    307                         Toast.makeText(getApplicationContext(),
    308                             getString(R.string.save_into) + " " + saveFolderName,
    309                             Toast.LENGTH_SHORT)
    310                             .show();
    311                         // TODO: change trimming into a service to avoid
    312                         // this progressDialog and add notification properly.
    313                         if (mProgress != null) {
    314                             mProgress.dismiss();
    315                             mProgress = null;
    316                             // Show the result only when the activity not stopped.
    317                             Intent intent = new Intent(android.content.Intent.ACTION_VIEW);
    318                             intent.setDataAndTypeAndNormalize(Uri.fromFile(mDstFile), "video/*");
    319                             intent.putExtra(MediaStore.EXTRA_FINISH_ON_COMPLETION, false);
    320                             startActivity(intent);
    321                             finish();
    322                         }
    323                     }
    324                 });
    325             }
    326         }).start();
    327     }
    328 
    329     private void showProgressDialog() {
    330         // create a background thread to trim the video.
    331         // and show the progress.
    332         mProgress = new ProgressDialog(this);
    333         mProgress.setTitle(getString(R.string.trimming));
    334         mProgress.setMessage(getString(R.string.please_wait));
    335         // TODO: make this cancelable.
    336         mProgress.setCancelable(false);
    337         mProgress.setCanceledOnTouchOutside(false);
    338         mProgress.show();
    339     }
    340 
    341     /**
    342      * Insert the content (saved file) with proper video properties.
    343      */
    344     private Uri insertContent(File file) {
    345         long nowInMs = System.currentTimeMillis();
    346         long nowInSec = nowInMs / 1000;
    347         final ContentValues values = new ContentValues(12);
    348         values.put(Video.Media.TITLE, mSaveFileName);
    349         values.put(Video.Media.DISPLAY_NAME, file.getName());
    350         values.put(Video.Media.MIME_TYPE, "video/mp4");
    351         values.put(Video.Media.DATE_TAKEN, nowInMs);
    352         values.put(Video.Media.DATE_MODIFIED, nowInSec);
    353         values.put(Video.Media.DATE_ADDED, nowInSec);
    354         values.put(Video.Media.DATA, file.getAbsolutePath());
    355         values.put(Video.Media.SIZE, file.length());
    356         // Copy the data taken and location info from src.
    357         String[] projection = new String[] {
    358                 VideoColumns.DATE_TAKEN,
    359                 VideoColumns.LATITUDE,
    360                 VideoColumns.LONGITUDE,
    361                 VideoColumns.RESOLUTION,
    362         };
    363 
    364         // Copy some info from the source file.
    365         querySource(projection, new ContentResolverQueryCallback() {
    366             @Override
    367             public void onCursorResult(Cursor cursor) {
    368                 long timeTaken = cursor.getLong(0);
    369                 if (timeTaken > 0) {
    370                     values.put(Video.Media.DATE_TAKEN, timeTaken);
    371                 }
    372                 double latitude = cursor.getDouble(1);
    373                 double longitude = cursor.getDouble(2);
    374                 // TODO: Change || to && after the default location issue is
    375                 // fixed.
    376                 if ((latitude != 0f) || (longitude != 0f)) {
    377                     values.put(Video.Media.LATITUDE, latitude);
    378                     values.put(Video.Media.LONGITUDE, longitude);
    379                 }
    380                 values.put(Video.Media.RESOLUTION, cursor.getString(3));
    381 
    382             }
    383         });
    384 
    385         return getContentResolver().insert(Video.Media.EXTERNAL_CONTENT_URI, values);
    386     }
    387 
    388     @Override
    389     public void onPlayPause() {
    390         if (mVideoView.isPlaying()) {
    391             pauseVideo();
    392         } else {
    393             playVideo();
    394         }
    395     }
    396 
    397     @Override
    398     public void onSeekStart() {
    399         pauseVideo();
    400     }
    401 
    402     @Override
    403     public void onSeekMove(int time) {
    404         mVideoView.seekTo(time);
    405     }
    406 
    407     @Override
    408     public void onSeekEnd(int time, int start, int end) {
    409         mVideoView.seekTo(time);
    410         mTrimStartTime = start;
    411         mTrimEndTime = end;
    412         setProgress();
    413     }
    414 
    415     @Override
    416     public void onShown() {
    417     }
    418 
    419     @Override
    420     public void onHidden() {
    421     }
    422 
    423     @Override
    424     public void onReplay() {
    425         mVideoView.seekTo(mTrimStartTime);
    426         playVideo();
    427     }
    428 
    429     @Override
    430     public void onCompletion(MediaPlayer mp) {
    431         mController.showEnded();
    432     }
    433 
    434     @Override
    435     public boolean onError(MediaPlayer mp, int what, int extra) {
    436         return false;
    437     }
    438 }
    439