• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.app.Service;
20 import android.content.ContentResolver;
21 import android.content.ContentValues;
22 import android.content.Intent;
23 import android.graphics.BitmapFactory;
24 import android.location.Location;
25 import android.net.Uri;
26 import android.os.AsyncTask;
27 import android.os.Binder;
28 import android.os.IBinder;
29 import android.provider.MediaStore.Video;
30 import android.util.Log;
31 
32 import com.android.camera.exif.ExifInterface;
33 
34 import java.io.File;
35 
36 /*
37  * Service for saving images in the background thread.
38  */
39 public class MediaSaveService extends Service {
40     public static final String VIDEO_BASE_URI = "content://media/external/video/media";
41 
42     // The memory limit for unsaved image is 20MB.
43     private static final int SAVE_TASK_MEMORY_LIMIT = 20 * 1024 * 1024;
44     private static final String TAG = "CAM_" + MediaSaveService.class.getSimpleName();
45 
46     private final IBinder mBinder = new LocalBinder();
47     private Listener mListener;
48     // Memory used by the total queued save request, in bytes.
49     private long mMemoryUse;
50 
51     public interface Listener {
onQueueStatus(boolean full)52         public void onQueueStatus(boolean full);
53     }
54 
55     public interface OnMediaSavedListener {
onMediaSaved(Uri uri)56         public void onMediaSaved(Uri uri);
57     }
58 
59     class LocalBinder extends Binder {
getService()60         public MediaSaveService getService() {
61             return MediaSaveService.this;
62         }
63     }
64 
65     @Override
onBind(Intent intent)66     public IBinder onBind(Intent intent) {
67         return mBinder;
68     }
69 
70     @Override
onStartCommand(Intent intent, int flag, int startId)71     public int onStartCommand(Intent intent, int flag, int startId) {
72         return START_STICKY;
73     }
74 
75     @Override
onDestroy()76     public void onDestroy() {
77     }
78 
79     @Override
onCreate()80     public void onCreate() {
81         mMemoryUse = 0;
82     }
83 
isQueueFull()84     public boolean isQueueFull() {
85         return (mMemoryUse >= SAVE_TASK_MEMORY_LIMIT);
86     }
87 
addImage(final byte[] data, String title, long date, Location loc, int width, int height, int orientation, ExifInterface exif, OnMediaSavedListener l, ContentResolver resolver)88     public void addImage(final byte[] data, String title, long date, Location loc,
89             int width, int height, int orientation, ExifInterface exif,
90             OnMediaSavedListener l, ContentResolver resolver) {
91         if (isQueueFull()) {
92             Log.e(TAG, "Cannot add image when the queue is full");
93             return;
94         }
95         ImageSaveTask t = new ImageSaveTask(data, title, date,
96                 (loc == null) ? null : new Location(loc),
97                 width, height, orientation, exif, resolver, l);
98 
99         mMemoryUse += data.length;
100         if (isQueueFull()) {
101             onQueueFull();
102         }
103         t.execute();
104     }
105 
addImage(final byte[] data, String title, long date, Location loc, int orientation, ExifInterface exif, OnMediaSavedListener l, ContentResolver resolver)106     public void addImage(final byte[] data, String title, long date, Location loc,
107                          int orientation, ExifInterface exif,
108                          OnMediaSavedListener l, ContentResolver resolver) {
109         // When dimensions are unknown, pass 0 as width and height,
110         // and decode image for width and height later in a background thread
111         addImage(data, title, date, loc, 0, 0, orientation, exif, l, resolver);
112     }
addImage(final byte[] data, String title, Location loc, int width, int height, int orientation, ExifInterface exif, OnMediaSavedListener l, ContentResolver resolver)113     public void addImage(final byte[] data, String title, Location loc,
114             int width, int height, int orientation, ExifInterface exif,
115             OnMediaSavedListener l, ContentResolver resolver) {
116         addImage(data, title, System.currentTimeMillis(), loc, width, height,
117                 orientation, exif, l, resolver);
118     }
119 
addVideo(String path, long duration, ContentValues values, OnMediaSavedListener l, ContentResolver resolver)120     public void addVideo(String path, long duration, ContentValues values,
121             OnMediaSavedListener l, ContentResolver resolver) {
122         // We don't set a queue limit for video saving because the file
123         // is already in the storage. Only updating the database.
124         new VideoSaveTask(path, duration, values, l, resolver).execute();
125     }
126 
setListener(Listener l)127     public void setListener(Listener l) {
128         mListener = l;
129         if (l == null) return;
130         l.onQueueStatus(isQueueFull());
131     }
132 
onQueueFull()133     private void onQueueFull() {
134         if (mListener != null) mListener.onQueueStatus(true);
135     }
136 
onQueueAvailable()137     private void onQueueAvailable() {
138         if (mListener != null) mListener.onQueueStatus(false);
139     }
140 
141     private class ImageSaveTask extends AsyncTask <Void, Void, Uri> {
142         private byte[] data;
143         private String title;
144         private long date;
145         private Location loc;
146         private int width, height;
147         private int orientation;
148         private ExifInterface exif;
149         private ContentResolver resolver;
150         private OnMediaSavedListener listener;
151 
ImageSaveTask(byte[] data, String title, long date, Location loc, int width, int height, int orientation, ExifInterface exif, ContentResolver resolver, OnMediaSavedListener listener)152         public ImageSaveTask(byte[] data, String title, long date, Location loc,
153                              int width, int height, int orientation, ExifInterface exif,
154                              ContentResolver resolver, OnMediaSavedListener listener) {
155             this.data = data;
156             this.title = title;
157             this.date = date;
158             this.loc = loc;
159             this.width = width;
160             this.height = height;
161             this.orientation = orientation;
162             this.exif = exif;
163             this.resolver = resolver;
164             this.listener = listener;
165         }
166 
167         @Override
onPreExecute()168         protected void onPreExecute() {
169             // do nothing.
170         }
171 
172         @Override
doInBackground(Void... v)173         protected Uri doInBackground(Void... v) {
174             if (width == 0 || height == 0) {
175                 // Decode bounds
176                 BitmapFactory.Options options = new BitmapFactory.Options();
177                 options.inJustDecodeBounds = true;
178                 BitmapFactory.decodeByteArray(data, 0, data.length, options);
179                 width = options.outWidth;
180                 height = options.outHeight;
181             }
182             return Storage.addImage(
183                     resolver, title, date, loc, orientation, exif, data, width, height);
184         }
185 
186         @Override
onPostExecute(Uri uri)187         protected void onPostExecute(Uri uri) {
188             if (listener != null) listener.onMediaSaved(uri);
189             boolean previouslyFull = isQueueFull();
190             mMemoryUse -= data.length;
191             if (isQueueFull() != previouslyFull) onQueueAvailable();
192         }
193     }
194 
195     private class VideoSaveTask extends AsyncTask <Void, Void, Uri> {
196         private String path;
197         private long duration;
198         private ContentValues values;
199         private OnMediaSavedListener listener;
200         private ContentResolver resolver;
201 
VideoSaveTask(String path, long duration, ContentValues values, OnMediaSavedListener l, ContentResolver r)202         public VideoSaveTask(String path, long duration, ContentValues values,
203                 OnMediaSavedListener l, ContentResolver r) {
204             this.path = path;
205             this.duration = duration;
206             this.values = new ContentValues(values);
207             this.listener = l;
208             this.resolver = r;
209         }
210 
211         @Override
doInBackground(Void... v)212         protected Uri doInBackground(Void... v) {
213             values.put(Video.Media.SIZE, new File(path).length());
214             values.put(Video.Media.DURATION, duration);
215             Uri uri = null;
216             try {
217                 Uri videoTable = Uri.parse(VIDEO_BASE_URI);
218                 uri = resolver.insert(videoTable, values);
219 
220                 // Rename the video file to the final name. This avoids other
221                 // apps reading incomplete data.  We need to do it after we are
222                 // certain that the previous insert to MediaProvider is completed.
223                 String finalName = values.getAsString(
224                         Video.Media.DATA);
225                 if (new File(path).renameTo(new File(finalName))) {
226                     path = finalName;
227                 }
228 
229                 resolver.update(uri, values, null, null);
230             } catch (Exception e) {
231                 // We failed to insert into the database. This can happen if
232                 // the SD card is unmounted.
233                 Log.e(TAG, "failed to add video to media store", e);
234                 uri = null;
235             } finally {
236                 Log.v(TAG, "Current video URI: " + uri);
237             }
238             return uri;
239         }
240 
241         @Override
onPostExecute(Uri uri)242         protected void onPostExecute(Uri uri) {
243             if (listener != null) listener.onMediaSaved(uri);
244         }
245     }
246 }
247