1 /*
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 
17 package com.android.camera;
18 
19 import android.content.ContentResolver;
20 import android.content.ContentValues;
21 import android.graphics.BitmapFactory;
22 import android.location.Location;
23 import android.net.Uri;
24 import android.os.AsyncTask;
25 import android.provider.MediaStore;
26 import android.provider.MediaStore.Video;
27 
28 import com.android.camera.app.MediaSaver;
29 import com.android.camera.data.FilmstripItemData;
30 import com.android.camera.debug.Log;
31 import com.android.camera.exif.ExifInterface;
32 
33 import java.io.File;
34 import java.io.IOException;
35 
36 /**
37  * A class implementing {@link com.android.camera.app.MediaSaver}.
38  */
39 public class MediaSaverImpl implements MediaSaver {
40     private static final Log.Tag TAG = new Log.Tag("MediaSaverImpl");
41 
42     /** The memory limit for unsaved image is 30MB. */
43     // TODO: Revert this back to 20 MB when CaptureSession API supports saving
44     // bursts.
45     private static final int SAVE_TASK_MEMORY_LIMIT = 30 * 1024 * 1024;
46 
47     private final ContentResolver mContentResolver;
48 
49     /** Memory used by the total queued save request, in bytes. */
50     private long mMemoryUse;
51 
52     private QueueListener mQueueListener;
53 
54     /**
55      * @param contentResolver The {@link android.content.ContentResolver} to be
56      *                 updated.
57      */
MediaSaverImpl(ContentResolver contentResolver)58     public MediaSaverImpl(ContentResolver contentResolver) {
59         mContentResolver = contentResolver;
60         mMemoryUse = 0;
61     }
62 
63     @Override
isQueueFull()64     public boolean isQueueFull() {
65         return (mMemoryUse >= SAVE_TASK_MEMORY_LIMIT);
66     }
67 
68     @Override
addImage(final byte[] data, String title, long date, Location loc, int width, int height, int orientation, ExifInterface exif, OnMediaSavedListener l)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         addImage(data, title, date, loc, width, height, orientation, exif, l,
72                 FilmstripItemData.MIME_TYPE_JPEG);
73     }
74 
75     @Override
addImage(final byte[] data, String title, long date, Location loc, int width, int height, int orientation, ExifInterface exif, OnMediaSavedListener l, String mimeType)76     public void addImage(final byte[] data, String title, long date, Location loc, int width,
77             int height, int orientation, ExifInterface exif, OnMediaSavedListener l,
78             String mimeType) {
79         if (isQueueFull()) {
80             Log.e(TAG, "Cannot add image when the queue is full");
81             return;
82         }
83         ImageSaveTask t = new ImageSaveTask(data, title, date,
84                 (loc == null) ? null : new Location(loc),
85                 width, height, orientation, mimeType, exif, mContentResolver, l);
86 
87         mMemoryUse += data.length;
88         if (isQueueFull()) {
89             onQueueFull();
90         }
91         t.execute();
92     }
93 
94     @Override
addImage(final byte[] data, String title, long date, Location loc, int orientation, ExifInterface exif, OnMediaSavedListener l)95     public void addImage(final byte[] data, String title, long date, Location loc, int orientation,
96             ExifInterface exif, OnMediaSavedListener l) {
97         // When dimensions are unknown, pass 0 as width and height,
98         // and decode image for width and height later in a background thread
99         addImage(data, title, date, loc, 0, 0, orientation, exif, l,
100                 FilmstripItemData.MIME_TYPE_JPEG);
101     }
102     @Override
addImage(final byte[] data, String title, Location loc, int width, int height, int orientation, ExifInterface exif, OnMediaSavedListener l)103     public void addImage(final byte[] data, String title, Location loc, int width, int height,
104             int orientation, ExifInterface exif, OnMediaSavedListener l) {
105         addImage(data, title, System.currentTimeMillis(), loc, width, height, orientation, exif, l,
106                 FilmstripItemData.MIME_TYPE_JPEG);
107     }
108 
109     @Override
addVideo(Uri uri, ContentValues values, OnMediaSavedListener l)110     public void addVideo(Uri uri, ContentValues values, OnMediaSavedListener l) {
111         // This updates the content resolver for the final saving of the video file.
112         new VideoSaveTask(uri, values, l, mContentResolver).execute();
113     }
114 
115     @Override
setQueueListener(QueueListener l)116     public void setQueueListener(QueueListener l) {
117         mQueueListener = l;
118         if (l == null) {
119             return;
120         }
121         l.onQueueStatus(isQueueFull());
122     }
123 
onQueueFull()124     private void onQueueFull() {
125         if (mQueueListener != null) {
126             mQueueListener.onQueueStatus(true);
127         }
128     }
129 
onQueueAvailable()130     private void onQueueAvailable() {
131         if (mQueueListener != null) {
132             mQueueListener.onQueueStatus(false);
133         }
134     }
135 
136     private class ImageSaveTask extends AsyncTask <Void, Void, Uri> {
137         private final byte[] data;
138         private final String title;
139         private final long date;
140         private final Location loc;
141         private int width, height;
142         private final int orientation;
143         private final String mimeType;
144         private final ExifInterface exif;
145         private final ContentResolver resolver;
146         private final OnMediaSavedListener listener;
147 
ImageSaveTask(byte[] data, String title, long date, Location loc, int width, int height, int orientation, String mimeType, ExifInterface exif, ContentResolver resolver, OnMediaSavedListener listener)148         public ImageSaveTask(byte[] data, String title, long date, Location loc,
149                              int width, int height, int orientation, String mimeType,
150                              ExifInterface exif, ContentResolver resolver,
151                              OnMediaSavedListener listener) {
152             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;
159             this.mimeType = mimeType;
160             this.exif = exif;
161             this.resolver = resolver;
162             this.listener = listener;
163         }
164 
165         @Override
onPreExecute()166         protected void onPreExecute() {
167             // do nothing.
168         }
169 
170         @Override
doInBackground(Void... v)171         protected Uri doInBackground(Void... v) {
172             if (width == 0 || height == 0) {
173                 // Decode bounds
174                 BitmapFactory.Options options = new BitmapFactory.Options();
175                 options.inJustDecodeBounds = true;
176                 BitmapFactory.decodeByteArray(data, 0, data.length, options);
177                 width = options.outWidth;
178                 height = options.outHeight;
179             }
180             try {
181                 return Storage.instance().addImage(
182                         resolver, title, date, loc, orientation, exif, data, width, height,
183                         mimeType);
184             } catch (IOException e) {
185                 Log.e(TAG, "Failed to write data", e);
186                 return null;
187             }
188         }
189 
190         @Override
onPostExecute(Uri uri)191         protected void onPostExecute(Uri uri) {
192             if (listener != null) {
193                 listener.onMediaSaved(uri);
194             }
195             boolean previouslyFull = isQueueFull();
196             mMemoryUse -= data.length;
197             if (isQueueFull() != previouslyFull) {
198                 onQueueAvailable();
199             }
200         }
201     }
202 
203     private class VideoSaveTask extends AsyncTask <Void, Void, Void> {
204         private final Uri uri;
205         private final ContentValues values;
206         private final OnMediaSavedListener listener;
207         private final ContentResolver resolver;
208 
VideoSaveTask(Uri u, ContentValues values, OnMediaSavedListener l, ContentResolver r)209         public VideoSaveTask(Uri u, ContentValues values, OnMediaSavedListener l,
210                              ContentResolver r) {
211             this.uri = u;
212             this.values = new ContentValues(values);
213             this.listener = l;
214             this.resolver = r;
215         }
216 
217         @Override
doInBackground(Void... v)218         protected Void doInBackground(Void... v) {
219             try {
220                 resolver.update(uri, values, null, null);
221             } catch (Exception e) {
222                 // We failed to update the database.
223                 Log.e(TAG, "failed to update video to media store", e);
224             } finally {
225                 Log.v(TAG, "Current video URI: " + uri);
226                 return null;
227             }
228         }
229 
230         @Override
onPostExecute(Void v)231         protected void onPostExecute(Void v) {
232             if (listener != null) {
233                 listener.onMediaSaved(uri);
234             }
235         }
236     }
237 }
238