OSDN Git Service

Video: Add the mute functionality.
authorTeng-Hui Zhu <ztenghui@google.com>
Tue, 13 Nov 2012 18:39:26 +0000 (10:39 -0800)
committerTeng-Hui Zhu <ztenghui@google.com>
Wed, 14 Nov 2012 19:34:05 +0000 (11:34 -0800)
1. Enable the mute video functionality.
2. Consolidate the sharing code, especially for file handling.

bug:7543943

Change-Id: Ie7ec605996bc3242b1a50385f506e6a50ee2d2fc

res/menu/photo.xml
res/values/strings.xml
src/com/android/gallery3d/app/MuteVideo.java [new file with mode: 0644]
src/com/android/gallery3d/app/PhotoPage.java
src/com/android/gallery3d/app/TrimVideo.java
src/com/android/gallery3d/app/VideoUtils.java [moved from src/com/android/gallery3d/app/TrimVideoUtils.java with 85% similarity]
src/com/android/gallery3d/data/LocalVideo.java
src/com/android/gallery3d/data/MediaObject.java
src/com/android/gallery3d/ui/MenuExecutor.java
src/com/android/gallery3d/util/SaveVideoFileInfo.java [new file with mode: 0644]
src/com/android/gallery3d/util/SaveVideoFileUtils.java [new file with mode: 0644]

index df3e4e8..16b2429 100644 (file)
@@ -59,6 +59,9 @@
     <item android:id="@+id/action_trim"
             android:title="@string/trim_action"
             android:showAsAction="never" />
+    <item android:id="@+id/action_mute"
+            android:title="@string/mute_action"
+            android:showAsAction="never" />
     <item android:id="@+id/action_setas"
             android:title="@string/set_image"
             android:showAsAction="never" />
index d62bfdc..ff4fe89 100644 (file)
          offline. [CHAR LIMIT=15] -->
     <string name="caching_label">Caching\u2026</string>
 
+    <!-- The title of the menu item to let user crop the image. [CHAR LIMIT=15] -->
     <string name="crop_action">Crop</string>
+    <!-- The title of the menu item to let user trim the video. [CHAR LIMIT=15] -->
     <string name="trim_action">Trim</string>
+    <!-- The title of the menu item to let user mute the video. [CHAR LIMIT=15] -->
+    <string name="mute_action">Mute</string>
+    <!-- The title of the menu item to let user set the image as background etc. [CHAR LIMIT=15] -->
     <string name="set_as">Set as</string>
 
-    <!-- String indicating an approximate location eg. Around Palo Alto, CA -->
+    <!-- String indicating an error when muting the video. [CHAR LIMIT=30] -->
+    <string name="video_mute_err">Can\'t mute video.</string>
+    <!-- String indicating an error when playing the video. [CHAR LIMIT=30] -->
     <string name="video_err">Can\'t play video.</string>
 
     <!-- Strings for grouping operations in the menu. The photos can be grouped
     <!-- The tilte of a dialog showing trimming in progress. [CHAR LIMIT=20] -->
     <string name="trimming">Trimming</string>
 
+    <!-- The tilte of a dialog showing muting in progress. [CHAR LIMIT=20] -->
+    <string name="muting">Muting</string>
+
     <!-- The content of a dialog showing trimming in progress. [CHAR LIMIT=30] -->
     <string name="please_wait">Please wait</string>
 
-    <!-- Toast after the trimming is done. [CHAR LIMIT=50] -->
-    <!-- TODO: this should be a format string!-->
-    <string name="save_into">Saving trimmed video into album :</string>
+    <!-- Toast after the trimming / muting is done. [CHAR LIMIT=50] -->
+    <string name="save_into">Saving video to <xliff:g id="album_name">%1$s</xliff:g> \u2026</string>
 
     <!-- Toast if the trimmed video is too short to trim. [CHAR LIMIT=80] -->
     <string name="trim_too_short">Can not trim : target video is too short</string>
diff --git a/src/com/android/gallery3d/app/MuteVideo.java b/src/com/android/gallery3d/app/MuteVideo.java
new file mode 100644 (file)
index 0000000..fbb1804
--- /dev/null
@@ -0,0 +1,105 @@
+/*
+ * 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.Activity;
+import android.app.ProgressDialog;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Handler;
+import android.provider.MediaStore;
+import android.widget.Toast;
+
+import com.android.gallery3d.R;
+import com.android.gallery3d.data.MediaItem;
+import com.android.gallery3d.util.SaveVideoFileInfo;
+import com.android.gallery3d.util.SaveVideoFileUtils;
+
+import java.io.IOException;
+
+public class MuteVideo {
+
+    private ProgressDialog mMuteProgress;
+
+    private MediaItem mCurrentItem = null;
+    private Uri mUri = null;
+    private SaveVideoFileInfo mDstFileInfo = null;
+    private Activity mActivity = null;
+    private final Handler mHandler = new Handler();
+
+    final String TIME_STAMP_NAME = "'MUTE'_yyyyMMdd_HHmmss";
+
+    public MuteVideo(MediaItem current, Uri uri, Activity activity) {
+        mUri = uri;
+        mCurrentItem = current;
+        mActivity = activity;
+    }
+
+    public void muteInBackground() {
+        mDstFileInfo = SaveVideoFileUtils.getDstMp4FileInfo(TIME_STAMP_NAME,
+                mActivity.getContentResolver(), mUri,
+                mActivity.getString(R.string.folder_download));
+
+        showProgressDialog();
+        new Thread(new Runnable() {
+                @Override
+            public void run() {
+                try {
+                    VideoUtils.startMute(mCurrentItem.getFilePath(), mDstFileInfo);
+                    SaveVideoFileUtils.insertContent(
+                            mDstFileInfo, mActivity.getContentResolver(), mUri);
+                } catch (IOException e) {
+                    Toast.makeText(mActivity, mActivity.getString(R.string.video_mute_err),
+                            Toast.LENGTH_SHORT).show();
+                }
+                // After muting is done, trigger the UI changed.
+                mHandler.post(new Runnable() {
+                        @Override
+                    public void run() {
+                        Toast.makeText(mActivity.getApplicationContext(),
+                                mActivity.getString(R.string.save_into,
+                                        mDstFileInfo.mFolderName),
+                                Toast.LENGTH_SHORT)
+                                .show();
+
+                        if (mMuteProgress != null) {
+                            mMuteProgress.dismiss();
+                            mMuteProgress = null;
+
+                            // Show the result only when the activity not
+                            // stopped.
+                            Intent intent = new Intent(android.content.Intent.ACTION_VIEW);
+                            intent.setDataAndTypeAndNormalize(
+                                    Uri.fromFile(mDstFileInfo.mFile), "video/*");
+                            intent.putExtra(MediaStore.EXTRA_FINISH_ON_COMPLETION, false);
+                            mActivity.startActivity(intent);
+                        }
+                    }
+                });
+            }
+        }).start();
+    }
+
+    private void showProgressDialog() {
+        mMuteProgress = new ProgressDialog(mActivity);
+        mMuteProgress.setTitle(mActivity.getString(R.string.muting));
+        mMuteProgress.setMessage(mActivity.getString(R.string.please_wait));
+        mMuteProgress.setCancelable(false);
+        mMuteProgress.setCanceledOnTouchOutside(false);
+        mMuteProgress.show();
+    }
+}
index da5465b..a3e85ac 100644 (file)
@@ -1069,6 +1069,12 @@ public abstract class PhotoPage extends ActivityState implements
                 mActivity.startActivityForResult(intent, REQUEST_TRIM);
                 return true;
             }
+            case R.id.action_mute: {
+                MuteVideo muteVideo = new MuteVideo(current,
+                        manager.getContentUri(path), mActivity);
+                muteVideo.muteInBackground();
+                return true;
+            }
             case R.id.action_edit: {
                 launchPhotoEditor();
                 return true;
index f246ff6..9187ee8 100644 (file)
@@ -41,6 +41,8 @@ import android.widget.VideoView;
 
 import com.android.gallery3d.R;
 import com.android.gallery3d.util.BucketNames;
+import com.android.gallery3d.util.SaveVideoFileInfo;
+import com.android.gallery3d.util.SaveVideoFileUtils;
 
 import java.io.File;
 import java.io.IOException;
@@ -71,13 +73,8 @@ public class TrimVideo extends Activity implements
     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;
+    private SaveVideoFileInfo mDstFileInfo = null;
 
     @Override
     public void onCreate(Bundle savedInstanceState) {
@@ -223,41 +220,6 @@ public class TrimVideo extends Activity implements
         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 boolean isModified() {
         int delta = mTrimEndTime - mTrimStartTime;
@@ -270,22 +232,12 @@ public class TrimVideo extends Activity implements
             return true;
         }
     }
+
     private void trimVideo() {
-        // 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);
+        mDstFileInfo = SaveVideoFileUtils.getDstMp4FileInfo(TIME_STAMP_NAME,
+                getContentResolver(), mUri, getString(R.string.folder_download));
+        final File mSrcFile = new File(mSrcVideoPath);
 
         showProgressDialog();
 
@@ -293,9 +245,11 @@ public class TrimVideo extends Activity implements
             @Override
             public void run() {
                 try {
-                    TrimVideoUtils.startTrim(mSrcFile, mDstFile, mTrimStartTime, mTrimEndTime);
+                    VideoUtils.startTrim(mSrcFile, mDstFileInfo.mFile,
+                            mTrimStartTime, mTrimEndTime, mVideoView.getDuration());
                     // Update the database for adding a new video file.
-                    insertContent(mDstFile);
+                    SaveVideoFileUtils.insertContent(mDstFileInfo,
+                            getContentResolver(), mUri);
                 } catch (IOException e) {
                     e.printStackTrace();
                 }
@@ -304,7 +258,7 @@ public class TrimVideo extends Activity implements
                     @Override
                     public void run() {
                         Toast.makeText(getApplicationContext(),
-                            getString(R.string.save_into) + " " + saveFolderName,
+                            getString(R.string.save_into, mDstFileInfo.mFolderName),
                             Toast.LENGTH_SHORT)
                             .show();
                         // TODO: change trimming into a service to avoid
@@ -314,7 +268,7 @@ public class TrimVideo extends Activity implements
                             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.setDataAndTypeAndNormalize(Uri.fromFile(mDstFileInfo.mFile), "video/*");
                             intent.putExtra(MediaStore.EXTRA_FINISH_ON_COMPLETION, false);
                             startActivity(intent);
                             finish();
@@ -337,53 +291,6 @@ public class TrimVideo extends Activity implements
         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()) {
@@ -19,6 +19,7 @@
 
 package com.android.gallery3d.app;
 
+import com.android.gallery3d.util.SaveVideoFileInfo;
 import com.coremedia.iso.IsoFile;
 import com.coremedia.iso.boxes.TimeToSampleBox;
 import com.googlecode.mp4parser.authoring.Movie;
@@ -36,12 +37,46 @@ import java.util.Arrays;
 import java.util.LinkedList;
 import java.util.List;
 
-/**
- * Shortens/Crops a track
- */
-public class TrimVideoUtils {
+public class VideoUtils {
 
-    public static void startTrim(File src, File dst, int startMs, int endMs) throws IOException {
+    public static void startMute(String filePath, SaveVideoFileInfo dstFileInfo) throws IOException {
+        File dst = dstFileInfo.mFile;
+        File src = new File(filePath);
+        RandomAccessFile randomAccessFile = new RandomAccessFile(src, "r");
+        Movie movie = MovieCreator.build(randomAccessFile.getChannel());
+
+        // remove all tracks we will create new tracks from the old
+        List<Track> tracks = movie.getTracks();
+        movie.setTracks(new LinkedList<Track>());
+
+        for (Track track : tracks) {
+            if (track.getHandler().equals("vide")) {
+                movie.addTrack(track);
+            }
+        }
+        writeMovieIntoFile(dst, movie);
+        randomAccessFile.close();
+    }
+
+    private static void writeMovieIntoFile(File dst, Movie movie)
+            throws IOException {
+        if (!dst.exists()) {
+            dst.createNewFile();
+        }
+
+        IsoFile out = new DefaultMp4Builder().build(movie);
+        FileOutputStream fos = new FileOutputStream(dst);
+        FileChannel fc = fos.getChannel();
+        out.getBox(fc);  // This one build up the memory.
+
+        fc.close();
+        fos.close();
+    }
+
+    /**
+     * Shortens/Crops a track
+     */
+    public static void startTrim(File src, File dst, int startMs, int endMs, int totalMs) throws IOException {
         RandomAccessFile randomAccessFile = new RandomAccessFile(src, "r");
         Movie movie = MovieCreator.build(randomAccessFile.getChannel());
 
@@ -100,18 +135,7 @@ public class TrimVideoUtils {
             }
             movie.addTrack(new CroppedTrack(track, startSample, endSample));
         }
-        IsoFile out = new DefaultMp4Builder().build(movie);
-
-        if (!dst.exists()) {
-            dst.createNewFile();
-        }
-
-        FileOutputStream fos = new FileOutputStream(dst);
-        FileChannel fc = fos.getChannel();
-        out.getBox(fc);  // This one build up the memory.
-
-        fc.close();
-        fos.close();
+        writeMovieIntoFile(dst, movie);
         randomAccessFile.close();
     }
 
index 44b8539..b1e1cb3 100644 (file)
@@ -180,7 +180,7 @@ public class LocalVideo extends LocalMediaItem {
 
     @Override
     public int getSupportedOperations() {
-        return SUPPORT_DELETE | SUPPORT_SHARE | SUPPORT_PLAY | SUPPORT_INFO | SUPPORT_TRIM;
+        return SUPPORT_DELETE | SUPPORT_SHARE | SUPPORT_PLAY | SUPPORT_INFO | SUPPORT_TRIM | SUPPORT_MUTE;
     }
 
     @Override
index a41b275..9c82661 100644 (file)
@@ -41,6 +41,7 @@ public abstract class MediaObject {
     public static final int SUPPORT_BACK = 1 << 14;
     public static final int SUPPORT_ACTION = 1 << 15;
     public static final int SUPPORT_CAMERA_SHORTCUT = 1 << 16;
+    public static final int SUPPORT_MUTE = 1 << 17;
     public static final int SUPPORT_ALL = 0xffffffff;
 
     // These are the bits returned from getMediaType():
index e37f5a5..3d088d1 100644 (file)
@@ -162,6 +162,7 @@ public class MenuExecutor {
         boolean supportRotate = (supported & MediaObject.SUPPORT_ROTATE) != 0;
         boolean supportCrop = (supported & MediaObject.SUPPORT_CROP) != 0;
         boolean supportTrim = (supported & MediaObject.SUPPORT_TRIM) != 0;
+        boolean supportMute = (supported & MediaObject.SUPPORT_MUTE) != 0;
         boolean supportShare = (supported & MediaObject.SUPPORT_SHARE) != 0;
         boolean supportSetAs = (supported & MediaObject.SUPPORT_SETAS) != 0;
         boolean supportShowOnMap = (supported & MediaObject.SUPPORT_SHOW_ON_MAP) != 0;
@@ -175,6 +176,7 @@ public class MenuExecutor {
         setMenuItemVisible(menu, R.id.action_rotate_cw, supportRotate);
         setMenuItemVisible(menu, R.id.action_crop, supportCrop);
         setMenuItemVisible(menu, R.id.action_trim, supportTrim);
+        setMenuItemVisible(menu, R.id.action_mute, supportMute);
         // Hide panorama until call to updateMenuForPanorama corrects it
         setMenuItemVisible(menu, R.id.action_share_panorama, false);
         setMenuItemVisible(menu, R.id.action_share, supportShare);
diff --git a/src/com/android/gallery3d/util/SaveVideoFileInfo.java b/src/com/android/gallery3d/util/SaveVideoFileInfo.java
new file mode 100644 (file)
index 0000000..c7e5e85
--- /dev/null
@@ -0,0 +1,29 @@
+/*
+ * 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.util;
+
+import java.io.File;
+
+public class SaveVideoFileInfo {
+    public File mFile = null;
+    public String mFileName = null;
+    // This the full directory path.
+    public File mDirectory = null;
+    // This is just the folder's name.
+    public String mFolderName = null;
+
+}
diff --git a/src/com/android/gallery3d/util/SaveVideoFileUtils.java b/src/com/android/gallery3d/util/SaveVideoFileUtils.java
new file mode 100644 (file)
index 0000000..c281dd3
--- /dev/null
@@ -0,0 +1,141 @@
+/*
+ * 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.util;
+
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Environment;
+import android.provider.MediaStore.Video;
+import android.provider.MediaStore.Video.VideoColumns;
+
+import java.io.File;
+import java.sql.Date;
+import java.text.SimpleDateFormat;
+
+public class SaveVideoFileUtils {
+    // Copy from SaveCopyTask.java in terms of how to handle the destination
+    // path and filename : querySource() and getSaveDirectory().
+    public interface ContentResolverQueryCallback {
+        void onCursorResult(Cursor cursor);
+    }
+
+    // This function can decide which folder to save the video file, and generate
+    // the needed information for the video file including filename.
+    public static SaveVideoFileInfo getDstMp4FileInfo(String fileNameFormat,
+            ContentResolver contentResolver, Uri uri, String defaultFolderName) {
+        SaveVideoFileInfo dstFileInfo = new SaveVideoFileInfo();
+        // Use the default save directory if the source directory cannot be
+        // saved.
+        dstFileInfo.mDirectory = getSaveDirectory(contentResolver, uri);
+        if ((dstFileInfo.mDirectory == null) || !dstFileInfo.mDirectory.canWrite()) {
+            dstFileInfo.mDirectory = new File(Environment.getExternalStorageDirectory(),
+                    BucketNames.DOWNLOAD);
+            dstFileInfo.mFolderName = defaultFolderName;
+        } else {
+            dstFileInfo.mFolderName = dstFileInfo.mDirectory.getName();
+        }
+        dstFileInfo.mFileName = new SimpleDateFormat(fileNameFormat).format(
+                new Date(System.currentTimeMillis()));
+
+        dstFileInfo.mFile = new File(dstFileInfo.mDirectory, dstFileInfo.mFileName + ".mp4");
+        return dstFileInfo;
+    }
+
+    private static void querySource(ContentResolver contentResolver, Uri uri,
+            String[] projection, ContentResolverQueryCallback callback) {
+        Cursor cursor = null;
+        try {
+            cursor = contentResolver.query(uri, 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 static File getSaveDirectory(ContentResolver contentResolver, Uri uri) {
+        final File[] dir = new File[1];
+        querySource(contentResolver, uri,
+                new String[] { VideoColumns.DATA },
+                new ContentResolverQueryCallback() {
+            @Override
+            public void onCursorResult(Cursor cursor) {
+                dir[0] = new File(cursor.getString(0)).getParentFile();
+            }
+        });
+        return dir[0];
+    }
+
+
+    /**
+     * Insert the content (saved file) with proper video properties.
+     */
+    public static Uri insertContent(SaveVideoFileInfo mDstFileInfo,
+            ContentResolver contentResolver, Uri uri ) {
+        long nowInMs = System.currentTimeMillis();
+        long nowInSec = nowInMs / 1000;
+        final ContentValues values = new ContentValues(12);
+        values.put(Video.Media.TITLE, mDstFileInfo.mFileName);
+        values.put(Video.Media.DISPLAY_NAME, mDstFileInfo.mFile.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, mDstFileInfo.mFile.getAbsolutePath());
+        values.put(Video.Media.SIZE, mDstFileInfo.mFile.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(contentResolver, uri, 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 contentResolver.insert(Video.Media.EXTERNAL_CONTENT_URI, values);
+    }
+
+}