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.Video;
26 
27 import com.android.camera.app.MediaSaver;
28 import com.android.camera.debug.Log;
29 import com.android.camera.exif.ExifInterface;
30 
31 import java.io.File;
32 
33 /**
34  * A class implementing {@link com.android.camera.app.MediaSaver}.
35  */
36 public class MediaSaverImpl implements MediaSaver {
37     private static final Log.Tag TAG = new Log.Tag("MediaSaverImpl");
38     private static final String VIDEO_BASE_URI = "content://media/external/video/media";
39 
40     /** The memory limit for unsaved image is 20MB. */
41     private static final int SAVE_TASK_MEMORY_LIMIT = 20 * 1024 * 1024;
42 
43     private QueueListener mQueueListener;
44 
45     /** Memory used by the total queued save request, in bytes. */
46     private long mMemoryUse;
47 
MediaSaverImpl()48     public MediaSaverImpl() {
49         mMemoryUse = 0;
50     }
51 
52     @Override
isQueueFull()53     public boolean isQueueFull() {
54         return (mMemoryUse >= SAVE_TASK_MEMORY_LIMIT);
55     }
56 
57     @Override
addImage(final byte[] data, String title, long date, Location loc, int width, int height, int orientation, ExifInterface exif, OnMediaSavedListener l, ContentResolver resolver)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) {
61         if (isQueueFull()) {
62             Log.e(TAG, "Cannot add image when the queue is full");
63             return;
64         }
65         ImageSaveTask t = new ImageSaveTask(data, title, date,
66                 (loc == null) ? null : new Location(loc),
67                 width, height, orientation, exif, resolver, l);
68 
69         mMemoryUse += data.length;
70         if (isQueueFull()) {
71             onQueueFull();
72         }
73         t.execute();
74     }
75 
76     @Override
addImage(final byte[] data, String title, long date, Location loc, int orientation, ExifInterface exif, OnMediaSavedListener l, ContentResolver resolver)77     public void addImage(final byte[] data, String title, long date, Location loc, int orientation,
78             ExifInterface exif, OnMediaSavedListener l, ContentResolver resolver) {
79         // 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     }
83     @Override
addImage(final byte[] data, String title, Location loc, int width, int height, int orientation, ExifInterface exif, OnMediaSavedListener l, ContentResolver resolver)84     public void addImage(final byte[] data, String title, Location loc, int width, int height,
85             int orientation, ExifInterface exif, OnMediaSavedListener l,
86             ContentResolver resolver) {
87         addImage(data, title, System.currentTimeMillis(), loc, width, height,
88                 orientation, exif, l, resolver);
89     }
90 
91     @Override
addVideo(String path, ContentValues values, OnMediaSavedListener l, ContentResolver resolver)92     public void addVideo(String path, ContentValues values, OnMediaSavedListener l,
93                          ContentResolver resolver) {
94         // We don't set a queue limit for video saving because the file
95         // is already in the storage. Only updating the database.
96         new VideoSaveTask(path, values, l, resolver).execute();
97     }
98 
99     @Override
setQueueListener(QueueListener l)100     public void setQueueListener(QueueListener l) {
101         mQueueListener = l;
102         if (l == null) {
103             return;
104         }
105         l.onQueueStatus(isQueueFull());
106     }
107 
onQueueFull()108     private void onQueueFull() {
109         if (mQueueListener != null) {
110             mQueueListener.onQueueStatus(true);
111         }
112     }
113 
onQueueAvailable()114     private void onQueueAvailable() {
115         if (mQueueListener != null) {
116             mQueueListener.onQueueStatus(false);
117         }
118     }
119 
120     private class ImageSaveTask extends AsyncTask <Void, Void, Uri> {
121         private final byte[] data;
122         private final String title;
123         private final long date;
124         private final Location loc;
125         private int width, height;
126         private final int orientation;
127         private final ExifInterface exif;
128         private final ContentResolver resolver;
129         private final OnMediaSavedListener listener;
130 
ImageSaveTask(byte[] data, String title, long date, Location loc, int width, int height, int orientation, ExifInterface exif, ContentResolver resolver, OnMediaSavedListener listener)131         public ImageSaveTask(byte[] data, String title, long date, Location loc,
132                              int width, int height, int orientation, ExifInterface exif,
133                              ContentResolver resolver, OnMediaSavedListener listener) {
134             this.data = data;
135             this.title = title;
136             this.date = date;
137             this.loc = loc;
138             this.width = width;
139             this.height = height;
140             this.orientation = orientation;
141             this.exif = exif;
142             this.resolver = resolver;
143             this.listener = listener;
144         }
145 
146         @Override
onPreExecute()147         protected void onPreExecute() {
148             // do nothing.
149         }
150 
151         @Override
doInBackground(Void... v)152         protected Uri doInBackground(Void... v) {
153             if (width == 0 || height == 0) {
154                 // Decode bounds
155                 BitmapFactory.Options options = new BitmapFactory.Options();
156                 options.inJustDecodeBounds = true;
157                 BitmapFactory.decodeByteArray(data, 0, data.length, options);
158                 width = options.outWidth;
159                 height = options.outHeight;
160             }
161             return Storage.addImage(
162                     resolver, title, date, loc, orientation, exif, data, width, height);
163         }
164 
165         @Override
onPostExecute(Uri uri)166         protected void onPostExecute(Uri uri) {
167             if (listener != null && uri != null) {
168                 listener.onMediaSaved(uri);
169             }
170             boolean previouslyFull = isQueueFull();
171             mMemoryUse -= data.length;
172             if (isQueueFull() != previouslyFull) {
173                 onQueueAvailable();
174             }
175         }
176     }
177 
178     private class VideoSaveTask extends AsyncTask <Void, Void, Uri> {
179         private String path;
180         private final ContentValues values;
181         private final OnMediaSavedListener listener;
182         private final ContentResolver resolver;
183 
VideoSaveTask(String path, ContentValues values, OnMediaSavedListener l, ContentResolver r)184         public VideoSaveTask(String path, ContentValues values, OnMediaSavedListener l,
185                              ContentResolver r) {
186             this.path = path;
187             this.values = new ContentValues(values);
188             this.listener = l;
189             this.resolver = r;
190         }
191 
192         @Override
doInBackground(Void... v)193         protected Uri doInBackground(Void... v) {
194             Uri uri = null;
195             try {
196                 Uri videoTable = Uri.parse(VIDEO_BASE_URI);
197                 uri = resolver.insert(videoTable, values);
198 
199                 // Rename the video file to the final name. This avoids other
200                 // apps reading incomplete data.  We need to do it after we are
201                 // certain that the previous insert to MediaProvider is completed.
202                 String finalName = values.getAsString(Video.Media.DATA);
203                 File finalFile = new File(finalName);
204                 if (new File(path).renameTo(finalFile)) {
205                     path = finalName;
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
onPostExecute(Uri uri)220         protected void onPostExecute(Uri uri) {
221             if (listener != null) {
222                 listener.onMediaSaved(uri);
223             }
224         }
225     }
226 }
227