blob: 1bcbcf2b3d12292b100ada882689a8b27f5e38ee [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
19import android.app.Service;
20import android.content.ContentResolver;
Angus Kong83a99ae2013-04-17 15:37:07 -070021import android.content.ContentValues;
Angus Kongce5480e2013-01-29 17:43:48 -080022import android.content.Intent;
Doris Liu6df2d962013-08-20 16:31:29 -070023import android.graphics.BitmapFactory;
Angus Kongce5480e2013-01-29 17:43:48 -080024import android.location.Location;
25import android.net.Uri;
26import android.os.AsyncTask;
27import android.os.Binder;
28import android.os.IBinder;
Angus Kong83a99ae2013-04-17 15:37:07 -070029import android.provider.MediaStore.Video;
Angus Kongce5480e2013-01-29 17:43:48 -080030import android.util.Log;
31
Angus Kongfd4fc0e2013-11-07 15:38:09 -080032import com.android.camera.app.MediaSaver;
ztenghuia16e7b52013-08-23 11:47:56 -070033import com.android.camera.exif.ExifInterface;
Angus Kong0d00a892013-03-26 11:40:40 -070034
Angus Kong83a99ae2013-04-17 15:37:07 -070035import java.io.File;
36
Angus Kongfd4fc0e2013-11-07 15:38:09 -080037/**
38 * A class implementing {@link com.android.camera.app.MediaSaver}.
Angus Kong86d36312013-01-31 18:22:44 -080039 */
Angus Kongfd4fc0e2013-11-07 15:38:09 -080040public class MediaSaveService extends Service implements MediaSaver {
Doris Liu3973deb2013-08-21 13:42:22 -070041 public static final String VIDEO_BASE_URI = "content://media/external/video/media";
42
Angus Kongc40c4112013-07-17 15:59:03 -070043 // The memory limit for unsaved image is 20MB.
44 private static final int SAVE_TASK_MEMORY_LIMIT = 20 * 1024 * 1024;
45 private static final String TAG = "CAM_" + MediaSaveService.class.getSimpleName();
Angus Kongce5480e2013-01-29 17:43:48 -080046
47 private final IBinder mBinder = new LocalBinder();
Angus Kongfd4fc0e2013-11-07 15:38:09 -080048 private QueueListener mQueueListener;
Angus Kongc40c4112013-07-17 15:59:03 -070049 // Memory used by the total queued save request, in bytes.
50 private long mMemoryUse;
Angus Kongce5480e2013-01-29 17:43:48 -080051
Angus Kongce5480e2013-01-29 17:43:48 -080052 class LocalBinder extends Binder {
Angus Kongfd4fc0e2013-11-07 15:38:09 -080053 public MediaSaver getService() {
Angus Kongce5480e2013-01-29 17:43:48 -080054 return MediaSaveService.this;
55 }
56 }
57
58 @Override
59 public IBinder onBind(Intent intent) {
60 return mBinder;
61 }
62
63 @Override
64 public int onStartCommand(Intent intent, int flag, int startId) {
65 return START_STICKY;
66 }
67
68 @Override
69 public void onDestroy() {
70 }
71
72 @Override
73 public void onCreate() {
Angus Kongc40c4112013-07-17 15:59:03 -070074 mMemoryUse = 0;
Angus Kongce5480e2013-01-29 17:43:48 -080075 }
76
Angus Kongfd4fc0e2013-11-07 15:38:09 -080077 @Override
Angus Kongce5480e2013-01-29 17:43:48 -080078 public boolean isQueueFull() {
Angus Kongc40c4112013-07-17 15:59:03 -070079 return (mMemoryUse >= SAVE_TASK_MEMORY_LIMIT);
Angus Kongce5480e2013-01-29 17:43:48 -080080 }
81
Angus Kongfd4fc0e2013-11-07 15:38:09 -080082 @Override
83 public void addImage(final byte[] data, String title, long date, Location loc, int width,
84 int height, int orientation, ExifInterface exif, OnMediaSavedListener l,
85 ContentResolver resolver) {
Angus Kongce5480e2013-01-29 17:43:48 -080086 if (isQueueFull()) {
87 Log.e(TAG, "Cannot add image when the queue is full");
88 return;
89 }
Angus Kong83a99ae2013-04-17 15:37:07 -070090 ImageSaveTask t = new ImageSaveTask(data, title, date,
91 (loc == null) ? null : new Location(loc),
Angus Kong0d00a892013-03-26 11:40:40 -070092 width, height, orientation, exif, resolver, l);
Angus Kongce5480e2013-01-29 17:43:48 -080093
Angus Kongc40c4112013-07-17 15:59:03 -070094 mMemoryUse += data.length;
Angus Kongce5480e2013-01-29 17:43:48 -080095 if (isQueueFull()) {
96 onQueueFull();
97 }
98 t.execute();
99 }
100
Angus Kongfd4fc0e2013-11-07 15:38:09 -0800101 @Override
102 public void addImage(final byte[] data, String title, long date, Location loc, int orientation,
103 ExifInterface exif, OnMediaSavedListener l, ContentResolver resolver) {
Doris Liu6df2d962013-08-20 16:31:29 -0700104 // When dimensions are unknown, pass 0 as width and height,
105 // and decode image for width and height later in a background thread
106 addImage(data, title, date, loc, 0, 0, orientation, exif, l, resolver);
107 }
Angus Kongfd4fc0e2013-11-07 15:38:09 -0800108 @Override
109 public void addImage(final byte[] data, String title, Location loc, int width, int height,
110 int orientation, ExifInterface exif, OnMediaSavedListener l, ContentResolver resolver) {
Angus Kongc40c4112013-07-17 15:59:03 -0700111 addImage(data, title, System.currentTimeMillis(), loc, width, height,
112 orientation, exif, l, resolver);
113 }
114
Angus Kongfd4fc0e2013-11-07 15:38:09 -0800115 @Override
116 public void addVideo(String path, long duration, ContentValues values, OnMediaSavedListener l,
117 ContentResolver resolver) {
Angus Kong83a99ae2013-04-17 15:37:07 -0700118 // We don't set a queue limit for video saving because the file
119 // is already in the storage. Only updating the database.
120 new VideoSaveTask(path, duration, values, l, resolver).execute();
121 }
122
Angus Kongfd4fc0e2013-11-07 15:38:09 -0800123 @Override
124 public void setQueueListener(QueueListener l) {
125 mQueueListener = l;
Angus Kongce5480e2013-01-29 17:43:48 -0800126 if (l == null) return;
Michael Kolbd6954f32013-03-08 20:43:01 -0800127 l.onQueueStatus(isQueueFull());
Angus Kongce5480e2013-01-29 17:43:48 -0800128 }
129
130 private void onQueueFull() {
Angus Kongfd4fc0e2013-11-07 15:38:09 -0800131 if (mQueueListener != null) mQueueListener.onQueueStatus(true);
Angus Kongce5480e2013-01-29 17:43:48 -0800132 }
133
134 private void onQueueAvailable() {
Angus Kongfd4fc0e2013-11-07 15:38:09 -0800135 if (mQueueListener != null) mQueueListener.onQueueStatus(false);
Angus Kongce5480e2013-01-29 17:43:48 -0800136 }
137
Angus Kong83a99ae2013-04-17 15:37:07 -0700138 private class ImageSaveTask extends AsyncTask <Void, Void, Uri> {
Angus Kongce5480e2013-01-29 17:43:48 -0800139 private byte[] data;
140 private String title;
141 private long date;
142 private Location loc;
143 private int width, height;
144 private int orientation;
Angus Kong0d00a892013-03-26 11:40:40 -0700145 private ExifInterface exif;
Angus Kongce5480e2013-01-29 17:43:48 -0800146 private ContentResolver resolver;
147 private OnMediaSavedListener listener;
148
Angus Kong83a99ae2013-04-17 15:37:07 -0700149 public ImageSaveTask(byte[] data, String title, long date, Location loc,
150 int width, int height, int orientation, ExifInterface exif,
151 ContentResolver resolver, OnMediaSavedListener listener) {
Angus Kongce5480e2013-01-29 17:43:48 -0800152 this.data = data;
153 this.title = title;
154 this.date = date;
155 this.loc = loc;
156 this.width = width;
157 this.height = height;
158 this.orientation = orientation;
Angus Kong0d00a892013-03-26 11:40:40 -0700159 this.exif = exif;
Angus Kongce5480e2013-01-29 17:43:48 -0800160 this.resolver = resolver;
161 this.listener = listener;
162 }
163
164 @Override
165 protected void onPreExecute() {
166 // do nothing.
167 }
168
169 @Override
170 protected Uri doInBackground(Void... v) {
Doris Liu6df2d962013-08-20 16:31:29 -0700171 if (width == 0 || height == 0) {
172 // Decode bounds
173 BitmapFactory.Options options = new BitmapFactory.Options();
174 options.inJustDecodeBounds = true;
175 BitmapFactory.decodeByteArray(data, 0, data.length, options);
176 width = options.outWidth;
177 height = options.outHeight;
178 }
Angus Kongce5480e2013-01-29 17:43:48 -0800179 return Storage.addImage(
Angus Kong0d00a892013-03-26 11:40:40 -0700180 resolver, title, date, loc, orientation, exif, data, width, height);
Angus Kongce5480e2013-01-29 17:43:48 -0800181 }
182
183 @Override
184 protected void onPostExecute(Uri uri) {
Angus Kong83a99ae2013-04-17 15:37:07 -0700185 if (listener != null) listener.onMediaSaved(uri);
Angus Kongc40c4112013-07-17 15:59:03 -0700186 boolean previouslyFull = isQueueFull();
187 mMemoryUse -= data.length;
188 if (isQueueFull() != previouslyFull) onQueueAvailable();
Angus Kongce5480e2013-01-29 17:43:48 -0800189 }
190 }
Angus Kong83a99ae2013-04-17 15:37:07 -0700191
192 private class VideoSaveTask extends AsyncTask <Void, Void, Uri> {
193 private String path;
194 private long duration;
195 private ContentValues values;
196 private OnMediaSavedListener listener;
197 private ContentResolver resolver;
198
199 public VideoSaveTask(String path, long duration, ContentValues values,
200 OnMediaSavedListener l, ContentResolver r) {
201 this.path = path;
202 this.duration = duration;
203 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) {
210 values.put(Video.Media.SIZE, new File(path).length());
211 values.put(Video.Media.DURATION, duration);
212 Uri uri = null;
213 try {
Doris Liu3973deb2013-08-21 13:42:22 -0700214 Uri videoTable = Uri.parse(VIDEO_BASE_URI);
Angus Kong83a99ae2013-04-17 15:37:07 -0700215 uri = resolver.insert(videoTable, values);
216
217 // Rename the video file to the final name. This avoids other
218 // apps reading incomplete data. We need to do it after we are
219 // certain that the previous insert to MediaProvider is completed.
220 String finalName = values.getAsString(
221 Video.Media.DATA);
222 if (new File(path).renameTo(new File(finalName))) {
223 path = finalName;
224 }
225
226 resolver.update(uri, values, null, null);
227 } catch (Exception e) {
228 // We failed to insert into the database. This can happen if
229 // the SD card is unmounted.
230 Log.e(TAG, "failed to add video to media store", e);
231 uri = null;
232 } finally {
233 Log.v(TAG, "Current video URI: " + uri);
234 }
235 return uri;
236 }
237
238 @Override
239 protected void onPostExecute(Uri uri) {
240 if (listener != null) listener.onMediaSaved(uri);
241 }
242 }
Angus Kongce5480e2013-01-29 17:43:48 -0800243}