blob: 5385bc69ce3e05d08cc024543ecfa60fe81b9b89 [file] [log] [blame]
Angus Kongce5480e2013-01-29 17:43:48 -08001/*
2 * Copyright (C) 2013 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
17package com.android.camera;
18
Angus Kongce5480e2013-01-29 17:43:48 -080019import android.content.ContentResolver;
Angus Kong83a99ae2013-04-17 15:37:07 -070020import android.content.ContentValues;
Doris Liu6df2d962013-08-20 16:31:29 -070021import android.graphics.BitmapFactory;
Angus Kongce5480e2013-01-29 17:43:48 -080022import android.location.Location;
23import android.net.Uri;
24import android.os.AsyncTask;
Angus Kong83a99ae2013-04-17 15:37:07 -070025import android.provider.MediaStore.Video;
Angus Kongce5480e2013-01-29 17:43:48 -080026
Angus Kongfd4fc0e2013-11-07 15:38:09 -080027import com.android.camera.app.MediaSaver;
Shashi Shekhar82d592f2014-11-12 09:52:19 -080028import com.android.camera.data.LocalData;
Angus Kong5596b4c2014-03-11 16:27:30 -070029import com.android.camera.debug.Log;
ztenghuia16e7b52013-08-23 11:47:56 -070030import com.android.camera.exif.ExifInterface;
Angus Kong0d00a892013-03-26 11:40:40 -070031
Angus Kong83a99ae2013-04-17 15:37:07 -070032import java.io.File;
33
Angus Kongfd4fc0e2013-11-07 15:38:09 -080034/**
35 * A class implementing {@link com.android.camera.app.MediaSaver}.
Angus Kong86d36312013-01-31 18:22:44 -080036 */
Sascha Haeberling280fd3e2013-11-21 13:52:15 -080037public class MediaSaverImpl implements MediaSaver {
Angus Kong5596b4c2014-03-11 16:27:30 -070038 private static final Log.Tag TAG = new Log.Tag("MediaSaverImpl");
Sascha Haeberling280fd3e2013-11-21 13:52:15 -080039 private static final String VIDEO_BASE_URI = "content://media/external/video/media";
Doris Liu3973deb2013-08-21 13:42:22 -070040
Shashi Shekhar57776072014-11-26 17:55:52 -080041 /** The memory limit for unsaved image is 30MB. */
42 // TODO: Revert this back to 20 MB when CaptureSession API supports saving
43 // bursts.
44 private static final int SAVE_TASK_MEMORY_LIMIT = 30 * 1024 * 1024;
Angus Kongce5480e2013-01-29 17:43:48 -080045
Angus Kongfd4fc0e2013-11-07 15:38:09 -080046 private QueueListener mQueueListener;
Sascha Haeberling280fd3e2013-11-21 13:52:15 -080047
48 /** Memory used by the total queued save request, in bytes. */
Angus Kongc40c4112013-07-17 15:59:03 -070049 private long mMemoryUse;
Angus Kongce5480e2013-01-29 17:43:48 -080050
Sascha Haeberling280fd3e2013-11-21 13:52:15 -080051 public MediaSaverImpl() {
Angus Kongc40c4112013-07-17 15:59:03 -070052 mMemoryUse = 0;
Angus Kongce5480e2013-01-29 17:43:48 -080053 }
54
Angus Kongfd4fc0e2013-11-07 15:38:09 -080055 @Override
Angus Kongce5480e2013-01-29 17:43:48 -080056 public boolean isQueueFull() {
Angus Kongc40c4112013-07-17 15:59:03 -070057 return (mMemoryUse >= SAVE_TASK_MEMORY_LIMIT);
Angus Kongce5480e2013-01-29 17:43:48 -080058 }
59
Angus Kongfd4fc0e2013-11-07 15:38:09 -080060 @Override
61 public void addImage(final byte[] data, String title, long date, Location loc, int width,
62 int height, int orientation, ExifInterface exif, OnMediaSavedListener l,
63 ContentResolver resolver) {
Shashi Shekhar82d592f2014-11-12 09:52:19 -080064 addImage(data, title, date, loc, width, height, orientation, exif, l,
65 resolver, LocalData.MIME_TYPE_JPEG);
66 }
67
68 @Override
69 public void addImage(final byte[] data, String title, long date, Location loc, int width,
70 int height, int orientation, ExifInterface exif, OnMediaSavedListener l,
71 ContentResolver resolver, String mimeType) {
Angus Kongce5480e2013-01-29 17:43:48 -080072 if (isQueueFull()) {
73 Log.e(TAG, "Cannot add image when the queue is full");
74 return;
75 }
Angus Kong83a99ae2013-04-17 15:37:07 -070076 ImageSaveTask t = new ImageSaveTask(data, title, date,
77 (loc == null) ? null : new Location(loc),
Shashi Shekhar82d592f2014-11-12 09:52:19 -080078 width, height, orientation, mimeType, exif, resolver, l);
Angus Kongce5480e2013-01-29 17:43:48 -080079
Angus Kongc40c4112013-07-17 15:59:03 -070080 mMemoryUse += data.length;
Angus Kongce5480e2013-01-29 17:43:48 -080081 if (isQueueFull()) {
82 onQueueFull();
83 }
84 t.execute();
85 }
86
Angus Kongfd4fc0e2013-11-07 15:38:09 -080087 @Override
88 public void addImage(final byte[] data, String title, long date, Location loc, int orientation,
89 ExifInterface exif, OnMediaSavedListener l, ContentResolver resolver) {
Doris Liu6df2d962013-08-20 16:31:29 -070090 // When dimensions are unknown, pass 0 as width and height,
91 // and decode image for width and height later in a background thread
Shashi Shekhar82d592f2014-11-12 09:52:19 -080092 addImage(data, title, date, loc, 0, 0, orientation, exif, l, resolver,
93 LocalData.MIME_TYPE_JPEG);
Doris Liu6df2d962013-08-20 16:31:29 -070094 }
Angus Kongfd4fc0e2013-11-07 15:38:09 -080095 @Override
96 public void addImage(final byte[] data, String title, Location loc, int width, int height,
Andy Huibers10c58162014-03-29 14:06:54 -070097 int orientation, ExifInterface exif, OnMediaSavedListener l,
98 ContentResolver resolver) {
Angus Kongc40c4112013-07-17 15:59:03 -070099 addImage(data, title, System.currentTimeMillis(), loc, width, height,
Shashi Shekhar82d592f2014-11-12 09:52:19 -0800100 orientation, exif, l, resolver, LocalData.MIME_TYPE_JPEG);
Angus Kongc40c4112013-07-17 15:59:03 -0700101 }
102
Angus Kongfd4fc0e2013-11-07 15:38:09 -0800103 @Override
Andy Huibers203abe52014-05-19 13:59:01 -0700104 public void addVideo(String path, ContentValues values, OnMediaSavedListener l,
105 ContentResolver resolver) {
Angus Kong83a99ae2013-04-17 15:37:07 -0700106 // We don't set a queue limit for video saving because the file
107 // is already in the storage. Only updating the database.
Andy Huibers203abe52014-05-19 13:59:01 -0700108 new VideoSaveTask(path, values, l, resolver).execute();
Angus Kong83a99ae2013-04-17 15:37:07 -0700109 }
110
Angus Kongfd4fc0e2013-11-07 15:38:09 -0800111 @Override
112 public void setQueueListener(QueueListener l) {
113 mQueueListener = l;
Sascha Haeberling280fd3e2013-11-21 13:52:15 -0800114 if (l == null) {
115 return;
116 }
Michael Kolbd6954f32013-03-08 20:43:01 -0800117 l.onQueueStatus(isQueueFull());
Angus Kongce5480e2013-01-29 17:43:48 -0800118 }
119
120 private void onQueueFull() {
Sascha Haeberling280fd3e2013-11-21 13:52:15 -0800121 if (mQueueListener != null) {
122 mQueueListener.onQueueStatus(true);
123 }
Angus Kongce5480e2013-01-29 17:43:48 -0800124 }
125
126 private void onQueueAvailable() {
Sascha Haeberling280fd3e2013-11-21 13:52:15 -0800127 if (mQueueListener != null) {
128 mQueueListener.onQueueStatus(false);
129 }
Angus Kongce5480e2013-01-29 17:43:48 -0800130 }
131
Angus Kong83a99ae2013-04-17 15:37:07 -0700132 private class ImageSaveTask extends AsyncTask <Void, Void, Uri> {
Sascha Haeberling280fd3e2013-11-21 13:52:15 -0800133 private final byte[] data;
134 private final String title;
135 private final long date;
136 private final Location loc;
Angus Kongce5480e2013-01-29 17:43:48 -0800137 private int width, height;
Sascha Haeberling280fd3e2013-11-21 13:52:15 -0800138 private final int orientation;
Shashi Shekhar82d592f2014-11-12 09:52:19 -0800139 private final String mimeType;
Sascha Haeberling280fd3e2013-11-21 13:52:15 -0800140 private final ExifInterface exif;
141 private final ContentResolver resolver;
142 private final OnMediaSavedListener listener;
Angus Kongce5480e2013-01-29 17:43:48 -0800143
Angus Kong83a99ae2013-04-17 15:37:07 -0700144 public ImageSaveTask(byte[] data, String title, long date, Location loc,
Shashi Shekhar82d592f2014-11-12 09:52:19 -0800145 int width, int height, int orientation, String mimeType,
146 ExifInterface exif, ContentResolver resolver,
147 OnMediaSavedListener listener) {
Angus Kongce5480e2013-01-29 17:43:48 -0800148 this.data = data;
149 this.title = title;
150 this.date = date;
151 this.loc = loc;
152 this.width = width;
153 this.height = height;
154 this.orientation = orientation;
Shashi Shekhar82d592f2014-11-12 09:52:19 -0800155 this.mimeType = mimeType;
Angus Kong0d00a892013-03-26 11:40:40 -0700156 this.exif = exif;
Angus Kongce5480e2013-01-29 17:43:48 -0800157 this.resolver = resolver;
158 this.listener = listener;
159 }
160
161 @Override
162 protected void onPreExecute() {
163 // do nothing.
164 }
165
166 @Override
167 protected Uri doInBackground(Void... v) {
Doris Liu6df2d962013-08-20 16:31:29 -0700168 if (width == 0 || height == 0) {
169 // Decode bounds
170 BitmapFactory.Options options = new BitmapFactory.Options();
171 options.inJustDecodeBounds = true;
172 BitmapFactory.decodeByteArray(data, 0, data.length, options);
173 width = options.outWidth;
174 height = options.outHeight;
175 }
Angus Kongce5480e2013-01-29 17:43:48 -0800176 return Storage.addImage(
Shashi Shekhar82d592f2014-11-12 09:52:19 -0800177 resolver, title, date, loc, orientation, exif, data, width, height,
178 mimeType);
Angus Kongce5480e2013-01-29 17:43:48 -0800179 }
180
181 @Override
182 protected void onPostExecute(Uri uri) {
Angus Kongc66ab6c2014-06-27 11:13:10 -0700183 if (listener != null && uri != null) {
184 listener.onMediaSaved(uri);
185 }
Angus Kongc40c4112013-07-17 15:59:03 -0700186 boolean previouslyFull = isQueueFull();
187 mMemoryUse -= data.length;
Angus Kongc66ab6c2014-06-27 11:13:10 -0700188 if (isQueueFull() != previouslyFull) {
189 onQueueAvailable();
190 }
Angus Kongce5480e2013-01-29 17:43:48 -0800191 }
192 }
Angus Kong83a99ae2013-04-17 15:37:07 -0700193
194 private class VideoSaveTask extends AsyncTask <Void, Void, Uri> {
195 private String path;
Sascha Haeberling280fd3e2013-11-21 13:52:15 -0800196 private final ContentValues values;
197 private final OnMediaSavedListener listener;
198 private final ContentResolver resolver;
Angus Kong83a99ae2013-04-17 15:37:07 -0700199
Andy Huibers203abe52014-05-19 13:59:01 -0700200 public VideoSaveTask(String path, ContentValues values, OnMediaSavedListener l,
201 ContentResolver r) {
Angus Kong83a99ae2013-04-17 15:37:07 -0700202 this.path = path;
Angus Kong83a99ae2013-04-17 15:37:07 -0700203 this.values = new ContentValues(values);
204 this.listener = l;
205 this.resolver = r;
206 }
207
208 @Override
Angus Kong83a99ae2013-04-17 15:37:07 -0700209 protected Uri doInBackground(Void... v) {
Angus Kong83a99ae2013-04-17 15:37:07 -0700210 Uri uri = null;
211 try {
Doris Liu3973deb2013-08-21 13:42:22 -0700212 Uri videoTable = Uri.parse(VIDEO_BASE_URI);
Angus Kong83a99ae2013-04-17 15:37:07 -0700213 uri = resolver.insert(videoTable, values);
214
215 // Rename the video file to the final name. This avoids other
216 // apps reading incomplete data. We need to do it after we are
217 // certain that the previous insert to MediaProvider is completed.
Andy Huibers10c58162014-03-29 14:06:54 -0700218 String finalName = values.getAsString(Video.Media.DATA);
Andy Huibers6388e632014-04-03 22:32:46 -0700219 File finalFile = new File(finalName);
220 if (new File(path).renameTo(finalFile)) {
Angus Kong83a99ae2013-04-17 15:37:07 -0700221 path = finalName;
222 }
Angus Kong83a99ae2013-04-17 15:37:07 -0700223 resolver.update(uri, values, null, null);
224 } catch (Exception e) {
225 // We failed to insert into the database. This can happen if
226 // the SD card is unmounted.
227 Log.e(TAG, "failed to add video to media store", e);
228 uri = null;
229 } finally {
230 Log.v(TAG, "Current video URI: " + uri);
231 }
232 return uri;
233 }
234
235 @Override
236 protected void onPostExecute(Uri uri) {
Angus Kongc66ab6c2014-06-27 11:13:10 -0700237 if (listener != null) {
238 listener.onMediaSaved(uri);
239 }
Angus Kong83a99ae2013-04-17 15:37:07 -0700240 }
241 }
Angus Kongce5480e2013-01-29 17:43:48 -0800242}