From 066d3100613a36b477597059556d04e7a9b7b4bd Mon Sep 17 00:00:00 2001 From: Shuzhen Wang Date: Tue, 27 Apr 2021 08:54:53 -0700 Subject: Camera2: Migrate recording path to scoped storage Use ContentResolver to store recorded video rather than using the app-specific storage. Test: Use Camera2 to record video, and verify using filmstrip/gallery Test: Camera Intents CtsVerifier test Bug: 185077726 Change-Id: I85e3d67c6ecb25869325f83250d872020aaa17cd --- src/com/android/camera/MediaSaverImpl.java | 41 +++++-------- src/com/android/camera/VideoModule.java | 94 +++++++++++++----------------- src/com/android/camera/app/MediaSaver.java | 6 +- 3 files changed, 57 insertions(+), 84 deletions(-) diff --git a/src/com/android/camera/MediaSaverImpl.java b/src/com/android/camera/MediaSaverImpl.java index a8e96ec97..781f7740b 100644 --- a/src/com/android/camera/MediaSaverImpl.java +++ b/src/com/android/camera/MediaSaverImpl.java @@ -22,6 +22,7 @@ import android.graphics.BitmapFactory; import android.location.Location; import android.net.Uri; import android.os.AsyncTask; +import android.provider.MediaStore; import android.provider.MediaStore.Video; import com.android.camera.app.MediaSaver; @@ -37,7 +38,6 @@ import java.io.IOException; */ public class MediaSaverImpl implements MediaSaver { private static final Log.Tag TAG = new Log.Tag("MediaSaverImpl"); - private static final String VIDEO_BASE_URI = "content://media/external/video/media"; /** The memory limit for unsaved image is 30MB. */ // TODO: Revert this back to 20 MB when CaptureSession API supports saving @@ -107,10 +107,9 @@ public class MediaSaverImpl implements MediaSaver { } @Override - public void addVideo(String path, ContentValues values, OnMediaSavedListener l) { - // We don't set a queue limit for video saving because the file - // is already in the storage. Only updating the database. - new VideoSaveTask(path, values, l, mContentResolver).execute(); + public void addVideo(Uri uri, ContentValues values, OnMediaSavedListener l) { + // This updates the content resolver for the final saving of the video file. + new VideoSaveTask(uri, values, l, mContentResolver).execute(); } @Override @@ -201,49 +200,35 @@ public class MediaSaverImpl implements MediaSaver { } } - private class VideoSaveTask extends AsyncTask { - private String path; + private class VideoSaveTask extends AsyncTask { + private final Uri uri; private final ContentValues values; private final OnMediaSavedListener listener; private final ContentResolver resolver; - public VideoSaveTask(String path, ContentValues values, OnMediaSavedListener l, + public VideoSaveTask(Uri u, ContentValues values, OnMediaSavedListener l, ContentResolver r) { - this.path = path; + this.uri = u; this.values = new ContentValues(values); this.listener = l; this.resolver = r; } @Override - protected Uri doInBackground(Void... v) { - Uri uri = null; + protected Void doInBackground(Void... v) { try { - Uri videoTable = Uri.parse(VIDEO_BASE_URI); - uri = resolver.insert(videoTable, values); - - // Rename the video file to the final name. This avoids other - // apps reading incomplete data. We need to do it after we are - // certain that the previous insert to MediaProvider is completed. - String finalName = values.getAsString(Video.Media.DATA); - File finalFile = new File(finalName); - if (new File(path).renameTo(finalFile)) { - path = finalName; - } resolver.update(uri, values, null, null); } catch (Exception e) { - // We failed to insert into the database. This can happen if - // the SD card is unmounted. - Log.e(TAG, "failed to add video to media store", e); - uri = null; + // We failed to update the database. + Log.e(TAG, "failed to update video to media store", e); } finally { Log.v(TAG, "Current video URI: " + uri); + return null; } - return uri; } @Override - protected void onPostExecute(Uri uri) { + protected void onPostExecute(Void v) { if (listener != null) { listener.onMediaSaved(uri); } diff --git a/src/com/android/camera/VideoModule.java b/src/com/android/camera/VideoModule.java index 856781b91..b0dee8d07 100644 --- a/src/com/android/camera/VideoModule.java +++ b/src/com/android/camera/VideoModule.java @@ -148,9 +148,6 @@ public class VideoModule extends CameraModule private long mRecordingStartTime; private boolean mRecordingTimeCountsDown = false; private long mOnResumeTime; - // The video file that the hardware camera is about to record into - // (or is recording into. - private String mVideoFilename; private ParcelFileDescriptor mVideoFileDescriptor; // The video file that has already been recorded, and that is being @@ -1087,16 +1084,6 @@ public class VideoModule extends CameraModule mActivity.finish(); } - private void cleanupEmptyFile() { - if (mVideoFilename != null) { - File f = new File(mVideoFilename); - if (f.length() == 0 && f.delete()) { - Log.v(TAG, "Empty video file deleted: " + mVideoFilename); - mVideoFilename = null; - } - } - } - // Prepares media recorder. private void initializeRecorder() { Log.i(TAG, "initializeRecorder: " + Thread.currentThread()); @@ -1124,7 +1111,23 @@ public class VideoModule extends CameraModule } } requestedSizeLimit = myExtras.getLong(MediaStore.EXTRA_SIZE_LIMIT); + } else { + generateVideoValues(); + Uri videoTable = MediaStore.Video.Media.getContentUri( + MediaStore.VOLUME_EXTERNAL_PRIMARY); + Uri videoUri = mContentResolver.insert(videoTable, mCurrentVideoValues); + + try { + mVideoFileDescriptor = + mContentResolver.openFileDescriptor(videoUri, "rw"); + mCurrentVideoUri = videoUri; + } catch (java.io.FileNotFoundException ex) { + // invalid uri + mContentResolver.delete(videoUri, null, null); + Log.e(TAG, ex.toString()); + } } + mMediaRecorder = new MediaRecorder(); // Unlock the camera object before passing it to media recorder. mCameraDevice.unlock(); @@ -1149,14 +1152,12 @@ public class VideoModule extends CameraModule setRecordLocation(); - // Set output file. - // Try Uri in the intent first. If it doesn't exist, use our own - // instead. + // Set output file using video Uri. if (mVideoFileDescriptor != null) { mMediaRecorder.setOutputFile(mVideoFileDescriptor.getFileDescriptor()); } else { - generateVideoFilename(mProfile.fileFormat); - mMediaRecorder.setOutputFile(mVideoFilename); + releaseMediaRecorder(); + throw new RuntimeException("No valid video file descriptor"); } // Set maximum file size. @@ -1185,7 +1186,7 @@ public class VideoModule extends CameraModule try { mMediaRecorder.prepare(); } catch (IOException e) { - Log.e(TAG, "prepare failed for " + mVideoFilename, e); + Log.e(TAG, "prepare failed", e); releaseMediaRecorder(); throw new RuntimeException(e); } @@ -1209,41 +1210,34 @@ public class VideoModule extends CameraModule private void releaseMediaRecorder() { Log.i(TAG, "Releasing media recorder."); if (mMediaRecorder != null) { - cleanupEmptyFile(); mMediaRecorder.reset(); mMediaRecorder.release(); mMediaRecorder = null; } - mVideoFilename = null; } - private void generateVideoFilename(int outputFileFormat) { + private void generateVideoValues() { long dateTaken = System.currentTimeMillis(); String title = createName(dateTaken); // Used when emailing. - String filename = title + convertOutputFormatToFileExt(outputFileFormat); - String mime = convertOutputFormatToMimeType(outputFileFormat); - String path = Storage.instance().DIRECTORY + '/' + filename; - String tmpPath = path + ".tmp"; + String mime = convertOutputFormatToMimeType(mProfile.fileFormat); mCurrentVideoValues = new ContentValues(9); mCurrentVideoValues.put(Video.Media.TITLE, title); - mCurrentVideoValues.put(Video.Media.DISPLAY_NAME, filename); + mCurrentVideoValues.put(Video.Media.DISPLAY_NAME, title); mCurrentVideoValues.put(Video.Media.DATE_TAKEN, dateTaken); mCurrentVideoValues.put(MediaColumns.DATE_MODIFIED, dateTaken / 1000); mCurrentVideoValues.put(Video.Media.MIME_TYPE, mime); - mCurrentVideoValues.put(Video.Media.DATA, path); mCurrentVideoValues.put(Video.Media.WIDTH, mProfile.videoFrameWidth); mCurrentVideoValues.put(Video.Media.HEIGHT, mProfile.videoFrameHeight); mCurrentVideoValues.put(Video.Media.RESOLUTION, Integer.toString(mProfile.videoFrameWidth) + "x" + Integer.toString(mProfile.videoFrameHeight)); + mCurrentVideoValues.put(Video.Media.IS_PENDING, 1); Location loc = mLocationManager.getCurrentLocation(); if (loc != null) { mCurrentVideoValues.put(Video.Media.LATITUDE, loc.getLatitude()); mCurrentVideoValues.put(Video.Media.LONGITUDE, loc.getLongitude()); } - mVideoFilename = tmpPath; - Log.v(TAG, "New video filename: " + mVideoFilename); } private void logVideoCapture(long duration) { @@ -1253,26 +1247,25 @@ public class VideoModule extends CameraModule boolean gridLinesOn = Keys.areGridLinesOn(mActivity.getSettingsManager()); int width = (Integer) mCurrentVideoValues.get(Video.Media.WIDTH); int height = (Integer) mCurrentVideoValues.get(Video.Media.HEIGHT); - long size = new File(mCurrentVideoFilename).length(); - String name = new File(mCurrentVideoValues.getAsString(Video.Media.DATA)).getName(); + long size = (Long) mCurrentVideoValues.get(Video.Media.SIZE); + String name = (String) mCurrentVideoValues.get(Video.Media.DISPLAY_NAME); UsageStatistics.instance().videoCaptureDoneEvent(name, duration, isCameraFrontFacing(), currentZoomValue(), width, height, size, flashSetting, gridLinesOn); } private void saveVideo() { - if (mVideoFileDescriptor == null) { - long duration = SystemClock.uptimeMillis() - mRecordingStartTime; - if (duration > 0) { - // - } else { - Log.w(TAG, "Video duration <= 0 : " + duration); - } - mCurrentVideoValues.put(Video.Media.SIZE, new File(mCurrentVideoFilename).length()); - mCurrentVideoValues.put(Video.Media.DURATION, duration); - getServices().getMediaSaver().addVideo(mCurrentVideoFilename, - mCurrentVideoValues, mOnVideoSavedListener); - logVideoCapture(duration); - } + long duration = SystemClock.uptimeMillis() - mRecordingStartTime; + if (duration > 0) { + // + } else { + Log.w(TAG, "Video duration <= 0 : " + duration); + } + mCurrentVideoValues.put(Video.Media.SIZE, mVideoFileDescriptor.getStatSize()); + mCurrentVideoValues.put(Video.Media.DURATION, duration); + mCurrentVideoValues.put(Video.Media.IS_PENDING, 0); + getServices().getMediaSaver().addVideo(mCurrentVideoUri, + mCurrentVideoValues, mOnVideoSavedListener); + logVideoCapture(duration); mCurrentVideoValues = null; } @@ -1489,13 +1482,8 @@ public class VideoModule extends CameraModule mMediaRecorder.setOnInfoListener(null); mMediaRecorder.stop(); shouldAddToMediaStoreNow = true; - mCurrentVideoFilename = mVideoFilename; - Log.v(TAG, "stopVideoRecording: current video filename: " + mCurrentVideoFilename); } catch (RuntimeException e) { Log.e(TAG, "stop fail", e); - if (mVideoFilename != null) { - deleteVideoFile(mVideoFilename); - } fail = true; } mMediaRecorderRecording = false; @@ -1517,11 +1505,11 @@ public class VideoModule extends CameraModule mUI.setOrientationIndicator(0, true); mActivity.enableKeepScreenOn(false); if (shouldAddToMediaStoreNow && !fail) { - if (mVideoFileDescriptor == null) { - saveVideo(); - } else if (mIsVideoCaptureIntent) { + if (mIsVideoCaptureIntent) { // if no file save is needed, we can show the post capture UI now showCaptureResult(); + } else { + saveVideo(); } } } diff --git a/src/com/android/camera/app/MediaSaver.java b/src/com/android/camera/app/MediaSaver.java index c96c55d20..2fca6cbd5 100644 --- a/src/com/android/camera/app/MediaSaver.java +++ b/src/com/android/camera/app/MediaSaver.java @@ -149,12 +149,12 @@ public interface MediaSaver { /** * Adds the video data into the {@link android.content.ContentResolver} in * the background. Only the database is updated here. The file should - * already be created by {@link android.media.MediaRecorder}. - * @param path The path of the video file on the storage. + * already be created by the ContentResolver.insert called earlier.. + * @param uri The Uri of the video file in the database. * @param values The values to be stored in the database. * @param l A callback object used when the saving is done. */ - void addVideo(String path, ContentValues values, OnMediaSavedListener l); + void addVideo(Uri uri, ContentValues values, OnMediaSavedListener l); /** * Sets the queue listener. -- cgit v1.2.3