blob: 029e9c32484f1f7e5adc3d6adb97f5d13b865668 [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;
Angus Kong2bca2102014-03-11 16:27:30 -070028import com.android.camera.debug.Log;
ztenghuia16e7b52013-08-23 11:47:56 -070029import com.android.camera.exif.ExifInterface;
Angus Kong0d00a892013-03-26 11:40:40 -070030
Angus Kong83a99ae2013-04-17 15:37:07 -070031import java.io.File;
32
Angus Kongfd4fc0e2013-11-07 15:38:09 -080033/**
34 * A class implementing {@link com.android.camera.app.MediaSaver}.
Angus Kong86d36312013-01-31 18:22:44 -080035 */
Sascha Haeberling280fd3e2013-11-21 13:52:15 -080036public class MediaSaverImpl implements MediaSaver {
Angus Kong2bca2102014-03-11 16:27:30 -070037 private static final Log.Tag TAG = new Log.Tag("MediaSaverImpl");
Sascha Haeberling280fd3e2013-11-21 13:52:15 -080038 private static final String VIDEO_BASE_URI = "content://media/external/video/media";
Doris Liu3973deb2013-08-21 13:42:22 -070039
Sascha Haeberling280fd3e2013-11-21 13:52:15 -080040 /** The memory limit for unsaved image is 20MB. */
Angus Kongc40c4112013-07-17 15:59:03 -070041 private static final int SAVE_TASK_MEMORY_LIMIT = 20 * 1024 * 1024;
Angus Kongce5480e2013-01-29 17:43:48 -080042
Angus Kongfd4fc0e2013-11-07 15:38:09 -080043 private QueueListener mQueueListener;
Sascha Haeberling280fd3e2013-11-21 13:52:15 -080044
45 /** Memory used by the total queued save request, in bytes. */
Angus Kongc40c4112013-07-17 15:59:03 -070046 private long mMemoryUse;
Angus Kongce5480e2013-01-29 17:43:48 -080047
Sascha Haeberling280fd3e2013-11-21 13:52:15 -080048 public MediaSaverImpl() {
Angus Kongc40c4112013-07-17 15:59:03 -070049 mMemoryUse = 0;
Angus Kongce5480e2013-01-29 17:43:48 -080050 }
51
Angus Kongfd4fc0e2013-11-07 15:38:09 -080052 @Override
Angus Kongce5480e2013-01-29 17:43:48 -080053 public boolean isQueueFull() {
Angus Kongc40c4112013-07-17 15:59:03 -070054 return (mMemoryUse >= SAVE_TASK_MEMORY_LIMIT);
Angus Kongce5480e2013-01-29 17:43:48 -080055 }
56
Angus Kongfd4fc0e2013-11-07 15:38:09 -080057 @Override
58 public void addImage(final byte[] data, String title, long date, Location loc, int width,
59 int height, int orientation, ExifInterface exif, OnMediaSavedListener l,
60 ContentResolver resolver) {
Angus Kongce5480e2013-01-29 17:43:48 -080061 if (isQueueFull()) {
62 Log.e(TAG, "Cannot add image when the queue is full");
63 return;
64 }
Angus Kong83a99ae2013-04-17 15:37:07 -070065 ImageSaveTask t = new ImageSaveTask(data, title, date,
66 (loc == null) ? null : new Location(loc),
Angus Kong0d00a892013-03-26 11:40:40 -070067 width, height, orientation, exif, resolver, l);
Angus Kongce5480e2013-01-29 17:43:48 -080068
Angus Kongc40c4112013-07-17 15:59:03 -070069 mMemoryUse += data.length;
Angus Kongce5480e2013-01-29 17:43:48 -080070 if (isQueueFull()) {
71 onQueueFull();
72 }
73 t.execute();
74 }
75
Angus Kongfd4fc0e2013-11-07 15:38:09 -080076 @Override
77 public void addImage(final byte[] data, String title, long date, Location loc, int orientation,
78 ExifInterface exif, OnMediaSavedListener l, ContentResolver resolver) {
Doris Liu6df2d962013-08-20 16:31:29 -070079 // When dimensions are unknown, pass 0 as width and height,
80 // and decode image for width and height later in a background thread
81 addImage(data, title, date, loc, 0, 0, orientation, exif, l, resolver);
82 }
Angus Kongfd4fc0e2013-11-07 15:38:09 -080083 @Override
84 public void addImage(final byte[] data, String title, Location loc, int width, int height,
85 int orientation, ExifInterface exif, OnMediaSavedListener l, ContentResolver resolver) {
Angus Kongc40c4112013-07-17 15:59:03 -070086 addImage(data, title, System.currentTimeMillis(), loc, width, height,
87 orientation, exif, l, resolver);
88 }
89
Angus Kongfd4fc0e2013-11-07 15:38:09 -080090 @Override
91 public void addVideo(String path, long duration, ContentValues values, OnMediaSavedListener l,
92 ContentResolver resolver) {
Angus Kong83a99ae2013-04-17 15:37:07 -070093 // We don't set a queue limit for video saving because the file
94 // is already in the storage. Only updating the database.
95 new VideoSaveTask(path, duration, values, l, resolver).execute();
96 }
97
Angus Kongfd4fc0e2013-11-07 15:38:09 -080098 @Override
99 public void setQueueListener(QueueListener l) {
100 mQueueListener = l;
Sascha Haeberling280fd3e2013-11-21 13:52:15 -0800101 if (l == null) {
102 return;
103 }
Michael Kolbd6954f32013-03-08 20:43:01 -0800104 l.onQueueStatus(isQueueFull());
Angus Kongce5480e2013-01-29 17:43:48 -0800105 }
106
107 private void onQueueFull() {
Sascha Haeberling280fd3e2013-11-21 13:52:15 -0800108 if (mQueueListener != null) {
109 mQueueListener.onQueueStatus(true);
110 }
Angus Kongce5480e2013-01-29 17:43:48 -0800111 }
112
113 private void onQueueAvailable() {
Sascha Haeberling280fd3e2013-11-21 13:52:15 -0800114 if (mQueueListener != null) {
115 mQueueListener.onQueueStatus(false);
116 }
Angus Kongce5480e2013-01-29 17:43:48 -0800117 }
118
Angus Kong83a99ae2013-04-17 15:37:07 -0700119 private class ImageSaveTask extends AsyncTask <Void, Void, Uri> {
Sascha Haeberling280fd3e2013-11-21 13:52:15 -0800120 private final byte[] data;
121 private final String title;
122 private final long date;
123 private final Location loc;
Angus Kongce5480e2013-01-29 17:43:48 -0800124 private int width, height;
Sascha Haeberling280fd3e2013-11-21 13:52:15 -0800125 private final int orientation;
126 private final ExifInterface exif;
127 private final ContentResolver resolver;
128 private final OnMediaSavedListener listener;
Angus Kongce5480e2013-01-29 17:43:48 -0800129
Angus Kong83a99ae2013-04-17 15:37:07 -0700130 public ImageSaveTask(byte[] data, String title, long date, Location loc,
131 int width, int height, int orientation, ExifInterface exif,
132 ContentResolver resolver, OnMediaSavedListener listener) {
Angus Kongce5480e2013-01-29 17:43:48 -0800133 this.data = data;
134 this.title = title;
135 this.date = date;
136 this.loc = loc;
137 this.width = width;
138 this.height = height;
139 this.orientation = orientation;
Angus Kong0d00a892013-03-26 11:40:40 -0700140 this.exif = exif;
Angus Kongce5480e2013-01-29 17:43:48 -0800141 this.resolver = resolver;
142 this.listener = listener;
143 }
144
145 @Override
146 protected void onPreExecute() {
147 // do nothing.
148 }
149
150 @Override
151 protected Uri doInBackground(Void... v) {
Doris Liu6df2d962013-08-20 16:31:29 -0700152 if (width == 0 || height == 0) {
153 // Decode bounds
154 BitmapFactory.Options options = new BitmapFactory.Options();
155 options.inJustDecodeBounds = true;
156 BitmapFactory.decodeByteArray(data, 0, data.length, options);
157 width = options.outWidth;
158 height = options.outHeight;
159 }
Angus Kongce5480e2013-01-29 17:43:48 -0800160 return Storage.addImage(
Angus Kong0d00a892013-03-26 11:40:40 -0700161 resolver, title, date, loc, orientation, exif, data, width, height);
Angus Kongce5480e2013-01-29 17:43:48 -0800162 }
163
164 @Override
165 protected void onPostExecute(Uri uri) {
Angus Kong83a99ae2013-04-17 15:37:07 -0700166 if (listener != null) listener.onMediaSaved(uri);
Angus Kongc40c4112013-07-17 15:59:03 -0700167 boolean previouslyFull = isQueueFull();
168 mMemoryUse -= data.length;
169 if (isQueueFull() != previouslyFull) onQueueAvailable();
Angus Kongce5480e2013-01-29 17:43:48 -0800170 }
171 }
Angus Kong83a99ae2013-04-17 15:37:07 -0700172
173 private class VideoSaveTask extends AsyncTask <Void, Void, Uri> {
174 private String path;
Sascha Haeberling280fd3e2013-11-21 13:52:15 -0800175 private final long duration;
176 private final ContentValues values;
177 private final OnMediaSavedListener listener;
178 private final ContentResolver resolver;
Angus Kong83a99ae2013-04-17 15:37:07 -0700179
180 public VideoSaveTask(String path, long duration, ContentValues values,
181 OnMediaSavedListener l, ContentResolver r) {
182 this.path = path;
183 this.duration = duration;
184 this.values = new ContentValues(values);
185 this.listener = l;
186 this.resolver = r;
187 }
188
189 @Override
Angus Kong83a99ae2013-04-17 15:37:07 -0700190 protected Uri doInBackground(Void... v) {
191 values.put(Video.Media.SIZE, new File(path).length());
192 values.put(Video.Media.DURATION, duration);
193 Uri uri = null;
194 try {
Doris Liu3973deb2013-08-21 13:42:22 -0700195 Uri videoTable = Uri.parse(VIDEO_BASE_URI);
Angus Kong83a99ae2013-04-17 15:37:07 -0700196 uri = resolver.insert(videoTable, values);
197
198 // Rename the video file to the final name. This avoids other
199 // apps reading incomplete data. We need to do it after we are
200 // certain that the previous insert to MediaProvider is completed.
201 String finalName = values.getAsString(
202 Video.Media.DATA);
203 if (new File(path).renameTo(new File(finalName))) {
204 path = finalName;
205 }
206
207 resolver.update(uri, values, null, null);
208 } catch (Exception e) {
209 // We failed to insert into the database. This can happen if
210 // the SD card is unmounted.
211 Log.e(TAG, "failed to add video to media store", e);
212 uri = null;
213 } finally {
214 Log.v(TAG, "Current video URI: " + uri);
215 }
216 return uri;
217 }
218
219 @Override
220 protected void onPostExecute(Uri uri) {
221 if (listener != null) listener.onMediaSaved(uri);
222 }
223 }
Angus Kongce5480e2013-01-29 17:43:48 -0800224}